From 394628f23dad014b18e70f249d043df95dcb4827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=BA=9A=E7=90=AA?= Date: Thu, 11 Nov 2021 15:50:14 +0800 Subject: [PATCH] feat: init project --- .gitignore | 6 + .npmignore | 8 + App.vue | 57 + LICENSE | 222 ++- README.en.md | 36 - README.md | 582 ++++++- changelog.md | 174 ++ common/appInit.js | 457 ++++++ common/openApp.js | 36 + common/uni-ui.scss | 118 ++ components/Sansnn-uQRCode/README.md | 172 ++ components/Sansnn-uQRCode/Sansnn-uQRCode.vue | 198 +++ components/Sansnn-uQRCode/uqrcode.js | 1438 +++++++++++++++++ components/cloud-image/cloud-image.vue | 70 + components/refreshBox/refreshBox.nvue | 95 ++ components/uni-agreements/uni-agreements.vue | 77 + .../uni-bindMobileByMpWeixin.vue | 189 +++ components/uni-load-state/i18n/en.json | 6 + components/uni-load-state/i18n/index.js | 6 + components/uni-load-state/i18n/zh-Hans.json | 6 + components/uni-load-state/readme.md | 3 + components/uni-load-state/uni-load-state.vue | 171 ++ .../uni-quick-login/uni-quick-login.vue | 396 +++++ components/uni-section/uni-section.vue | 139 ++ .../uni-send-sms-code/uni-send-sms-code.vue | 144 ++ .../uni-user-profile/uni-user-profile.vue | 194 +++ index.html | 14 + lang/en.js | 199 +++ lang/i18n.js | 102 ++ lang/zh-Hans.js | 200 +++ main.js | 29 + manifest.json | 229 +++ package.json | 85 + pages.json | 240 +++ pages/common/webview/webview.vue | 39 + pages/grid/grid.vue | 222 +++ pages/list/detail.vue | 368 +++++ pages/list/list.nvue | 238 +++ pages/list/search/search.nvue | 506 ++++++ pages/ucenter/about/about.vue | 203 +++ pages/ucenter/guestbook/guestbook.vue | 223 +++ pages/ucenter/invite/invite.vue | 179 ++ .../ucenter/login-page/common/login-page.css | 61 + .../login-page/common/login-page.mixin.js | 15 + .../ucenter/login-page/common/loginSuccess.js | 20 + pages/ucenter/login-page/index/index.vue | 133 ++ .../login-page/phone-code/phone-code.vue | 72 + .../login-page/pwd-login/pwd-login.vue | 164 ++ .../login-page/pwd-retrieve/pwd-retrieve.vue | 176 ++ .../ucenter/login-page/register/register.vue | 105 ++ .../ucenter/login-page/register/validator.js | 61 + pages/ucenter/read-news-log/read-news-log.vue | 78 + pages/ucenter/settings/dc-push/push.js | 118 ++ .../settings/deactivate/deactivate.vue | 103 ++ pages/ucenter/settings/settings.vue | 344 ++++ pages/ucenter/ucenter.vue | 449 +++++ .../userinfo/bind-mobile/bind-mobile.vue | 115 ++ pages/ucenter/userinfo/cropImage.vue | 38 + pages/ucenter/userinfo/limeClipper/README.md | 227 +++ .../userinfo/limeClipper/images/photo.svg | 19 + .../userinfo/limeClipper/images/rotate.svg | 15 + pages/ucenter/userinfo/limeClipper/index.css | 160 ++ .../userinfo/limeClipper/limeClipper.vue | 816 ++++++++++ pages/ucenter/userinfo/limeClipper/utils.js | 244 +++ pages/ucenter/userinfo/userinfo.vue | 284 ++++ pages/uni-agree/uni-agree.nvue | 139 ++ pages/uni-agree/utils/uni-agree.js | 11 + static/app-plus/sharemenu/copyurl.png | Bin 0 -> 920 bytes static/app-plus/sharemenu/more.png | Bin 0 -> 1538 bytes static/app-plus/sharemenu/mp_weixin.png | Bin 0 -> 8250 bytes static/app-plus/sharemenu/qq.png | Bin 0 -> 1605 bytes static/app-plus/sharemenu/wechatfriend.png | Bin 0 -> 2024 bytes static/app-plus/sharemenu/wechatmoments.png | Bin 0 -> 1758 bytes static/app-plus/sharemenu/weibo.png | Bin 0 -> 2274 bytes static/grid/c1.png | Bin 0 -> 401 bytes static/grid/c2.png | Bin 0 -> 470 bytes static/grid/c3.png | Bin 0 -> 511 bytes static/grid/c4.png | Bin 0 -> 476 bytes static/grid/c5.png | Bin 0 -> 472 bytes static/grid/c6.png | Bin 0 -> 545 bytes static/grid/c7.png | Bin 0 -> 365 bytes static/grid/c8.png | Bin 0 -> 587 bytes static/grid/c9.png | Bin 0 -> 565 bytes static/grid/empty.png | Bin 0 -> 5333 bytes static/h5/download-app/android.png | Bin 0 -> 1490 bytes static/h5/download-app/ios.png | Bin 0 -> 1592 bytes static/h5/download-app/openImg.png | Bin 0 -> 21351 bytes static/limeClipper/photo.svg | 19 + static/limeClipper/rotate.svg | 15 + static/login-index/apple.png | Bin 0 -> 18205 bytes static/login-index/weixin.png | Bin 0 -> 13348 bytes static/logo.png | Bin 0 -> 4023 bytes static/tabbar/grid.png | Bin 0 -> 5177 bytes static/tabbar/grid_active.png | Bin 0 -> 4757 bytes static/tabbar/im-contacts.png | Bin 0 -> 4946 bytes static/tabbar/im-contacts_active.png | Bin 0 -> 5191 bytes static/tabbar/list.png | Bin 0 -> 6130 bytes static/tabbar/list_active.png | Bin 0 -> 5264 bytes static/tabbar/me.png | Bin 0 -> 6771 bytes static/tabbar/me_active.png | Bin 0 -> 5960 bytes static/uni-center/defaultAvatarUrl.png | Bin 0 -> 5947 bytes static/uni-center/grey.png | Bin 0 -> 6669 bytes static/uni-center/headers.png | Bin 0 -> 27733 bytes static/uni-load-state/disconnection.png | Bin 0 -> 19467 bytes static/uni-quick-login/apple.png | Bin 0 -> 9709 bytes static/uni-quick-login/sms.png | Bin 0 -> 9153 bytes static/uni-quick-login/univerify.png | Bin 0 -> 6793 bytes static/uni-quick-login/user.png | Bin 0 -> 6307 bytes static/uni-quick-login/wechat.png | Bin 0 -> 8754 bytes static/uni-sign-in/background.png | Bin 0 -> 93495 bytes static/uni.ttf | Bin 0 -> 26164 bytes store/index.js | 24 + store/modules/user.js | 48 + uni-starter.config.js | 92 ++ uni.scss | 76 + .../uni-analyse-searchhot/index.js | 49 + .../uni-analyse-searchhot/package.json | 14 + ...6\345\272\223\347\256\241\347\220\206.jql" | 12 + .../database/JQL\346\237\245\350\257\242.jql" | 9 + uniCloud-aliyun/database/db_init.json | 157 ++ uniCloud-aliyun/database/default.jql | 12 + .../database/guestbook.schema.json | 57 + .../database/opendb-admin-menus.schema.json | 56 + .../database/opendb-app-versions.schema.json | 124 ++ .../database/opendb-banner.schema.json | 54 + .../database/opendb-department.schema.json | 47 + .../database/opendb-mall-goods.schema.json | 123 ++ .../opendb-news-articles-detail.schema.json | 122 ++ .../database/opendb-news-articles.schema.json | 165 ++ .../opendb-news-categories.schema.json | 50 + .../database/opendb-news-comments.schema.json | 68 + .../database/opendb-news-favorite.schema.json | 46 + .../database/opendb-search-hot.schema.json | 27 + .../database/opendb-search-log.schema.json | 31 + .../database/opendb-verify-codes.schema.json | 45 + .../database/read-news-log.schema.json | 35 + .../database/uni-id-device.schema.json | 64 + .../database/uni-id-log.schema.json | 41 + .../database/uni-id-permissions.schema.json | 46 + .../database/uni-id-roles.schema.json | 46 + .../database/uni-id-scores.schema.json | 44 + .../database/uni-id-users.schema.json | 366 +++++ uni_modules.config.json | 6 + uni_modules/json-gps/changelog.md | 2 + uni_modules/json-gps/js_sdk/gps.js | 133 ++ .../js_sdk/wa-permission/permission.js | 272 ++++ uni_modules/json-gps/package.json | 76 + uni_modules/json-gps/readme.md | 30 + .../json-interceptor-chooseImage/changelog.md | 6 + .../js_sdk/main.js | 70 + .../json-interceptor-chooseImage/package.json | 76 + .../json-interceptor-chooseImage/readme.md | 30 + uni_modules/uni-badge/changelog.md | 22 + .../components/uni-badge/uni-badge.vue | 253 +++ uni_modules/uni-badge/package.json | 88 + uni_modules/uni-badge/readme.md | 58 + uni_modules/uni-calendar/changelog.md | 10 + .../components/uni-calendar/calendar.js | 546 +++++++ .../components/uni-calendar/i18n/en.json | 12 + .../components/uni-calendar/i18n/index.js | 8 + .../components/uni-calendar/i18n/zh-Hans.json | 12 + .../components/uni-calendar/i18n/zh-Hant.json | 12 + .../uni-calendar/uni-calendar-item.vue | 181 +++ .../components/uni-calendar/uni-calendar.vue | 547 +++++++ .../components/uni-calendar/util.js | 352 ++++ uni_modules/uni-calendar/package.json | 88 + uni_modules/uni-calendar/readme.md | 103 ++ uni_modules/uni-captcha/changelog.md | 4 + uni_modules/uni-captcha/package.json | 80 + uni_modules/uni-captcha/readme.md | 92 ++ .../common/uni-captcha/LICENSE.md | 201 +++ .../common/uni-captcha/index.js | 1 + .../common/uni-captcha/package.json | 13 + uni_modules/uni-card/changelog.md | 12 + .../uni-card/components/uni-card/uni-card.vue | 431 +++++ uni_modules/uni-card/package.json | 85 + uni_modules/uni-card/readme.md | 104 ++ uni_modules/uni-collapse/changelog.md | 27 + .../uni-collapse-item/uni-collapse-item.vue | 402 +++++ .../components/uni-collapse/uni-collapse.vue | 146 ++ uni_modules/uni-collapse/package.json | 88 + uni_modules/uni-collapse/readme.md | 276 ++++ uni_modules/uni-combox/changelog.md | 10 + .../components/uni-combox/uni-combox.vue | 239 +++ uni_modules/uni-combox/package.json | 85 + uni_modules/uni-combox/readme.md | 52 + uni_modules/uni-config-center/changelog.md | 4 + uni_modules/uni-config-center/package.json | 80 + uni_modules/uni-config-center/readme.md | 93 ++ .../common/uni-config-center/index.js | 1 + .../common/uni-config-center/package.json | 9 + .../uni-config-center/uni-id/config.json | 52 + uni_modules/uni-countdown/changelog.md | 14 + .../components/uni-countdown/i18n/en.json | 6 + .../components/uni-countdown/i18n/index.js | 8 + .../uni-countdown/i18n/zh-Hans.json | 6 + .../uni-countdown/i18n/zh-Hant.json | 6 + .../uni-countdown/uni-countdown.vue | 260 +++ uni_modules/uni-countdown/package.json | 86 + uni_modules/uni-countdown/readme.md | 57 + uni_modules/uni-data-checkbox/changelog.md | 36 + .../uni-data-checkbox/uni-data-checkbox.vue | 823 ++++++++++ uni_modules/uni-data-checkbox/package.json | 87 + uni_modules/uni-data-checkbox/readme.md | 299 ++++ uni_modules/uni-data-picker/changelog.md | 25 + .../components/uni-data-picker/config.json | 12 + .../components/uni-data-picker/keypress.js | 45 + .../uni-data-picker/uni-data-picker.vue | 472 ++++++ .../uni-data-pickerview/uni-data-picker.js | 545 +++++++ .../uni-data-pickerview.vue | 300 ++++ uni_modules/uni-data-picker/package.json | 86 + uni_modules/uni-data-picker/readme.md | 270 ++++ uni_modules/uni-dateformat/changelog.md | 7 + .../components/uni-dateformat/date-format.js | 200 +++ .../uni-dateformat/uni-dateformat.vue | 88 + uni_modules/uni-dateformat/package.json | 84 + uni_modules/uni-dateformat/readme.md | 77 + uni_modules/uni-datetime-picker/changelog.md | 65 + .../uni-datetime-picker/calendar-item.vue | 183 +++ .../uni-datetime-picker/calendar.js | 546 +++++++ .../uni-datetime-picker/calendar.vue | 801 +++++++++ .../uni-datetime-picker/i18n/en.json | 19 + .../uni-datetime-picker/i18n/index.js | 8 + .../uni-datetime-picker/i18n/zh-Hans.json | 19 + .../uni-datetime-picker/i18n/zh-Hant.json | 19 + .../uni-datetime-picker/keypress.js | 45 + .../uni-datetime-picker/time-picker.vue | 924 +++++++++++ .../uni-datetime-picker.vue | 951 +++++++++++ .../components/uni-datetime-picker/util.js | 408 +++++ uni_modules/uni-datetime-picker/package.json | 89 + uni_modules/uni-datetime-picker/readme.md | 155 ++ uni_modules/uni-drawer/changelog.md | 8 + .../components/uni-drawer/keypress.js | 45 + .../components/uni-drawer/uni-drawer.vue | 182 +++ uni_modules/uni-drawer/package.json | 83 + uni_modules/uni-drawer/readme.md | 86 + uni_modules/uni-easyinput/changelog.md | 28 + .../components/uni-easyinput/common.js | 56 + .../uni-easyinput/uni-easyinput.vue | 461 ++++++ uni_modules/uni-easyinput/package.json | 89 + uni_modules/uni-easyinput/readme.md | 198 +++ uni_modules/uni-fab/changelog.md | 8 + .../uni-fab/components/uni-fab/uni-fab.vue | 448 +++++ .../components/uni-fab/uni-fab.vue.bak | 383 +++++ uni_modules/uni-fab/package.json | 83 + uni_modules/uni-fab/readme.md | 91 ++ uni_modules/uni-fav/changelog.md | 14 + .../uni-fav/components/uni-fav/i18n/en.json | 4 + .../uni-fav/components/uni-fav/i18n/index.js | 8 + .../components/uni-fav/i18n/zh-Hans.json | 4 + .../components/uni-fav/i18n/zh-Hant.json | 4 + .../uni-fav/components/uni-fav/uni-fav.vue | 156 ++ uni_modules/uni-fav/package.json | 88 + uni_modules/uni-fav/readme.md | 50 + uni_modules/uni-feedback/changelog.md | 2 + .../js_sdk/validator/opendb-feedback.js | 98 ++ uni_modules/uni-feedback/package.json | 88 + .../pages/opendb-feedback/detail.vue | 113 ++ .../pages/opendb-feedback/edit.vue | 167 ++ .../pages/opendb-feedback/list.vue | 70 + .../pages/opendb-feedback/opendb-feedback.vue | 140 ++ uni_modules/uni-feedback/readme.md | 2 + .../database/opendb-feedback.schema.json | 72 + uni_modules/uni-file-picker/changelog.md | 52 + .../uni-file-picker/choose-and-upload-file.js | 224 +++ .../uni-file-picker/uni-file-picker.vue | 652 ++++++++ .../uni-file-picker/upload-file.vue | 325 ++++ .../uni-file-picker/upload-image.vue | 290 ++++ .../components/uni-file-picker/utils.js | 109 ++ uni_modules/uni-file-picker/package.json | 86 + uni_modules/uni-file-picker/readme.md | 305 ++++ uni_modules/uni-forms/changelog.md | 53 + .../uni-forms-item/uni-forms-item.vue | 509 ++++++ .../components/uni-forms/uni-forms.vue | 472 ++++++ .../components/uni-forms/validate.js | 486 ++++++ uni_modules/uni-forms/package.json | 89 + uni_modules/uni-forms/readme.md | 830 ++++++++++ uni_modules/uni-goods-nav/changelog.md | 13 + .../components/uni-goods-nav/i18n/en.json | 6 + .../components/uni-goods-nav/i18n/index.js | 8 + .../uni-goods-nav/i18n/zh-Hans.json | 6 + .../uni-goods-nav/i18n/zh-Hant.json | 6 + .../uni-goods-nav/uni-goods-nav.vue | 232 +++ uni_modules/uni-goods-nav/package.json | 87 + uni_modules/uni-goods-nav/readme.md | 111 ++ uni_modules/uni-grid/changelog.md | 8 + .../uni-grid-item/uni-grid-item.vue | 127 ++ .../uni-grid/components/uni-grid/uni-grid.vue | 142 ++ uni_modules/uni-grid/package.json | 82 + uni_modules/uni-grid/readme.md | 95 ++ uni_modules/uni-group/changelog.md | 8 + .../components/uni-group/uni-group.vue | 130 ++ uni_modules/uni-group/package.json | 83 + uni_modules/uni-group/readme.md | 54 + uni_modules/uni-icons/changelog.md | 8 + .../uni-icons/components/uni-icons/icons.js | 132 ++ .../components/uni-icons/uni-icons.vue | 72 + .../uni-icons/components/uni-icons/uni.ttf | Bin 0 -> 26164 bytes uni_modules/uni-icons/package.json | 86 + uni_modules/uni-icons/readme.md | 64 + uni_modules/uni-id-cf/changelog.md | 19 + uni_modules/uni-id-cf/package.json | 81 + uni_modules/uni-id-cf/readme.md | 7 + .../cloudfunctions/uni-id-cf/index.js | 580 +++++++ .../cloudfunctions/uni-id-cf/package.json | 16 + uni_modules/uni-id/changelog.md | 54 + uni_modules/uni-id/package.json | 84 + uni_modules/uni-id/readme.md | 33 + .../cloudfunctions/common/uni-id/LICENSE.md | 201 +++ .../cloudfunctions/common/uni-id/index.js | 1 + .../cloudfunctions/common/uni-id/package.json | 16 + uni_modules/uni-image-menu/changelog.md | 0 .../uni-image-menu/js_sdk/uni-image-menu.js | 169 ++ uni_modules/uni-image-menu/package.json | 76 + uni_modules/uni-image-menu/readme.md | 19 + uni_modules/uni-indexed-list/changelog.md | 12 + .../uni-indexed-list-item.vue | 142 ++ .../uni-indexed-list/uni-indexed-list.vue | 359 ++++ uni_modules/uni-indexed-list/package.json | 84 + uni_modules/uni-indexed-list/readme.md | 67 + uni_modules/uni-link/changelog.md | 11 + .../uni-link/components/uni-link/uni-link.vue | 120 ++ uni_modules/uni-link/package.json | 83 + uni_modules/uni-link/readme.md | 48 + uni_modules/uni-list/changelog.md | 15 + .../components/uni-list-ad/uni-list-ad.vue | 107 ++ .../uni-list-chat/uni-list-chat.scss | 58 + .../uni-list-chat/uni-list-chat.vue | 534 ++++++ .../uni-list-item/uni-list-item.vue | 461 ++++++ .../uni-list/components/uni-list/uni-list.vue | 106 ++ .../components/uni-list/uni-refresh.vue | 65 + .../components/uni-list/uni-refresh.wxs | 87 + uni_modules/uni-list/package.json | 91 ++ uni_modules/uni-list/readme.md | 347 ++++ uni_modules/uni-load-more/changelog.md | 10 + .../components/uni-load-more/i18n/en.json | 5 + .../components/uni-load-more/i18n/index.js | 8 + .../uni-load-more/i18n/zh-Hans.json | 5 + .../uni-load-more/i18n/zh-Hant.json | 5 + .../uni-load-more/uni-load-more.vue | 378 +++++ uni_modules/uni-load-more/package.json | 86 + uni_modules/uni-load-more/readme.md | 70 + uni_modules/uni-nav-bar/changelog.md | 19 + .../components/uni-nav-bar/uni-nav-bar.vue | 252 +++ .../components/uni-nav-bar/uni-status-bar.vue | 27 + uni_modules/uni-nav-bar/package.json | 84 + uni_modules/uni-nav-bar/readme.md | 71 + uni_modules/uni-notice-bar/changelog.md | 11 + .../uni-notice-bar/uni-notice-bar.vue | 398 +++++ uni_modules/uni-notice-bar/package.json | 85 + uni_modules/uni-notice-bar/readme.md | 71 + uni_modules/uni-number-box/changelog.md | 18 + .../uni-number-box/uni-number-box.vue | 231 +++ uni_modules/uni-number-box/package.json | 81 + uni_modules/uni-number-box/readme.md | 50 + uni_modules/uni-pagination/changelog.md | 11 + .../uni-pagination/uni-pagination.vue | 404 +++++ uni_modules/uni-pagination/package.json | 82 + uni_modules/uni-pagination/readme.md | 48 + uni_modules/uni-popup/changelog.md | 37 + .../components/uni-popup-dialog/keypress.js | 45 + .../uni-popup-dialog/uni-popup-dialog.vue | 263 +++ .../uni-popup-message/uni-popup-message.vue | 143 ++ .../uni-popup-share/uni-popup-share.vue | 185 +++ .../components/uni-popup/i18n/en.json | 7 + .../components/uni-popup/i18n/index.js | 8 + .../components/uni-popup/i18n/zh-Hans.json | 7 + .../components/uni-popup/i18n/zh-Hant.json | 7 + .../components/uni-popup/keypress.js | 45 + .../uni-popup/components/uni-popup/message.js | 22 + .../uni-popup/components/uni-popup/popup.js | 26 + .../uni-popup/components/uni-popup/share.js | 16 + .../components/uni-popup/uni-popup.vue | 403 +++++ uni_modules/uni-popup/package.json | 89 + uni_modules/uni-popup/readme.md | 296 ++++ uni_modules/uni-rate/changelog.md | 18 + .../uni-rate/components/uni-rate/uni-rate.vue | 393 +++++ uni_modules/uni-rate/package.json | 83 + uni_modules/uni-rate/readme.md | 107 ++ uni_modules/uni-row/changelog.md | 7 + .../uni-row/components/uni-col/uni-col.vue | 317 ++++ .../uni-row/components/uni-row/uni-row.vue | 190 +++ uni_modules/uni-row/package.json | 83 + uni_modules/uni-row/readme.md | 183 +++ uni_modules/uni-search-bar/changelog.md | 22 + .../components/uni-search-bar/i18n/en.json | 4 + .../components/uni-search-bar/i18n/index.js | 8 + .../uni-search-bar/i18n/zh-Hans.json | 4 + .../uni-search-bar/i18n/zh-Hant.json | 4 + .../uni-search-bar/uni-search-bar.vue | 282 ++++ uni_modules/uni-search-bar/package.json | 88 + uni_modules/uni-search-bar/readme.md | 86 + .../uni-segmented-control/changelog.md | 6 + .../uni-segmented-control.vue | 142 ++ .../uni-segmented-control/package.json | 83 + uni_modules/uni-segmented-control/readme.md | 60 + uni_modules/uni-share/changelog.md | 14 + .../uni-share/js_sdk/uni-image-menu.js | 203 +++ uni_modules/uni-share/js_sdk/uni-share.js | 98 ++ uni_modules/uni-share/package.json | 80 + uni_modules/uni-share/readme.md | 85 + uni_modules/uni-sign-in/changelog.md | 6 + .../components/uni-sign-in/uni-sign-in.vue | 232 +++ uni_modules/uni-sign-in/package.json | 82 + uni_modules/uni-sign-in/pages/demo/demo.vue | 12 + uni_modules/uni-sign-in/readme.md | 49 + .../uni-clientDB-actions/signIn.js | 90 ++ .../database/opendb-sign-in.schema.json | 41 + uni_modules/uni-steps/changelog.md | 11 + .../components/uni-steps/uni-steps.vue | 254 +++ uni_modules/uni-steps/package.json | 84 + uni_modules/uni-steps/readme.md | 49 + uni_modules/uni-swipe-action/changelog.md | 24 + .../uni-swipe-action-item/bindingx.js | 300 ++++ .../uni-swipe-action-item/index.wxs | 323 ++++ .../components/uni-swipe-action-item/isPC.js | 12 + .../uni-swipe-action-item/mpalipay.js | 193 +++ .../uni-swipe-action-item/mpother.js | 258 +++ .../components/uni-swipe-action-item/mpwxs.js | 81 + .../uni-swipe-action-item/render.js | 265 +++ .../uni-swipe-action-item.vue | 348 ++++ .../components/uni-swipe-action-item/wx.wxs | 341 ++++ .../uni-swipe-action/uni-swipe-action.vue | 60 + uni_modules/uni-swipe-action/package.json | 87 + uni_modules/uni-swipe-action/readme.md | 193 +++ uni_modules/uni-swiper-dot/changelog.md | 9 + .../uni-swiper-dot/uni-swiper-dot.vue | 216 +++ uni_modules/uni-swiper-dot/package.json | 83 + uni_modules/uni-swiper-dot/readme.md | 91 ++ uni_modules/uni-table/changelog.md | 18 + .../components/uni-table/uni-table.vue | 455 ++++++ .../components/uni-tbody/uni-tbody.vue | 29 + .../uni-table/components/uni-td/uni-td.vue | 90 ++ .../components/uni-th/filter-dropdown.vue | 503 ++++++ .../uni-table/components/uni-th/uni-th.vue | 251 +++ .../components/uni-thead/uni-thead.vue | 129 ++ .../components/uni-tr/table-checkbox.vue | 179 ++ .../uni-table/components/uni-tr/uni-tr.vue | 162 ++ uni_modules/uni-table/package.json | 82 + uni_modules/uni-table/readme.md | 148 ++ uni_modules/uni-tag/changelog.md | 14 + .../uni-tag/components/uni-tag/uni-tag.vue | 279 ++++ uni_modules/uni-tag/package.json | 87 + uni_modules/uni-tag/readme.md | 49 + uni_modules/uni-title/changelog.md | 5 + .../components/uni-title/uni-title.vue | 171 ++ uni_modules/uni-title/package.json | 84 + uni_modules/uni-title/readme.md | 71 + uni_modules/uni-transition/changelog.md | 13 + .../uni-transition/createAnimation.js | 128 ++ .../uni-transition/uni-transition.vue | 277 ++++ uni_modules/uni-transition/package.json | 83 + uni_modules/uni-transition/readme.md | 397 +++++ uni_modules/uni-ui/changelog.md | 251 +++ .../uni-ui/components/uni-ui/uni-ui.vue | 7 + uni_modules/uni-ui/package.json | 128 ++ uni_modules/uni-ui/readme.md | 240 +++ .../uni-upgrade-center-app/changelog.md | 45 + .../uni-upgrade-center-app/package.json | 80 + .../pages/upgrade-popup.vue | 506 ++++++ .../uni-upgrade-center-app/pages_init.json | 18 + uni_modules/uni-upgrade-center-app/readme.md | 119 ++ .../static/app_update_close.png | Bin 0 -> 7644 bytes .../uni-upgrade-center-app/static/bg_top.png | Bin 0 -> 30486 bytes .../static/button_bg.png | Bin 0 -> 4156 bytes .../check-version/check-version.param.json | 9 + .../cloudfunctions/check-version/index.js | 167 ++ .../utils/call-check-version.js | 29 + .../utils/check-update.js | 155 ++ .../database/opendb-app-list.schema.json | 62 + 470 files changed, 57451 insertions(+), 84 deletions(-) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 App.vue delete mode 100644 README.en.md create mode 100644 changelog.md create mode 100644 common/appInit.js create mode 100644 common/openApp.js create mode 100644 common/uni-ui.scss create mode 100644 components/Sansnn-uQRCode/README.md create mode 100644 components/Sansnn-uQRCode/Sansnn-uQRCode.vue create mode 100644 components/Sansnn-uQRCode/uqrcode.js create mode 100644 components/cloud-image/cloud-image.vue create mode 100644 components/refreshBox/refreshBox.nvue create mode 100644 components/uni-agreements/uni-agreements.vue create mode 100644 components/uni-bindMobileByMpWeixin/uni-bindMobileByMpWeixin.vue create mode 100644 components/uni-load-state/i18n/en.json create mode 100644 components/uni-load-state/i18n/index.js create mode 100644 components/uni-load-state/i18n/zh-Hans.json create mode 100644 components/uni-load-state/readme.md create mode 100644 components/uni-load-state/uni-load-state.vue create mode 100644 components/uni-quick-login/uni-quick-login.vue create mode 100644 components/uni-section/uni-section.vue create mode 100644 components/uni-send-sms-code/uni-send-sms-code.vue create mode 100644 components/uni-user-profile/uni-user-profile.vue create mode 100644 index.html create mode 100644 lang/en.js create mode 100644 lang/i18n.js create mode 100644 lang/zh-Hans.js create mode 100644 main.js create mode 100644 manifest.json create mode 100644 package.json create mode 100644 pages.json create mode 100644 pages/common/webview/webview.vue create mode 100644 pages/grid/grid.vue create mode 100644 pages/list/detail.vue create mode 100644 pages/list/list.nvue create mode 100644 pages/list/search/search.nvue create mode 100644 pages/ucenter/about/about.vue create mode 100644 pages/ucenter/guestbook/guestbook.vue create mode 100644 pages/ucenter/invite/invite.vue create mode 100644 pages/ucenter/login-page/common/login-page.css create mode 100644 pages/ucenter/login-page/common/login-page.mixin.js create mode 100644 pages/ucenter/login-page/common/loginSuccess.js create mode 100644 pages/ucenter/login-page/index/index.vue create mode 100644 pages/ucenter/login-page/phone-code/phone-code.vue create mode 100644 pages/ucenter/login-page/pwd-login/pwd-login.vue create mode 100644 pages/ucenter/login-page/pwd-retrieve/pwd-retrieve.vue create mode 100644 pages/ucenter/login-page/register/register.vue create mode 100644 pages/ucenter/login-page/register/validator.js create mode 100644 pages/ucenter/read-news-log/read-news-log.vue create mode 100644 pages/ucenter/settings/dc-push/push.js create mode 100644 pages/ucenter/settings/deactivate/deactivate.vue create mode 100644 pages/ucenter/settings/settings.vue create mode 100644 pages/ucenter/ucenter.vue create mode 100644 pages/ucenter/userinfo/bind-mobile/bind-mobile.vue create mode 100644 pages/ucenter/userinfo/cropImage.vue create mode 100644 pages/ucenter/userinfo/limeClipper/README.md create mode 100644 pages/ucenter/userinfo/limeClipper/images/photo.svg create mode 100644 pages/ucenter/userinfo/limeClipper/images/rotate.svg create mode 100644 pages/ucenter/userinfo/limeClipper/index.css create mode 100644 pages/ucenter/userinfo/limeClipper/limeClipper.vue create mode 100644 pages/ucenter/userinfo/limeClipper/utils.js create mode 100644 pages/ucenter/userinfo/userinfo.vue create mode 100644 pages/uni-agree/uni-agree.nvue create mode 100644 pages/uni-agree/utils/uni-agree.js create mode 100644 static/app-plus/sharemenu/copyurl.png create mode 100644 static/app-plus/sharemenu/more.png create mode 100644 static/app-plus/sharemenu/mp_weixin.png create mode 100644 static/app-plus/sharemenu/qq.png create mode 100644 static/app-plus/sharemenu/wechatfriend.png create mode 100644 static/app-plus/sharemenu/wechatmoments.png create mode 100644 static/app-plus/sharemenu/weibo.png create mode 100644 static/grid/c1.png create mode 100644 static/grid/c2.png create mode 100644 static/grid/c3.png create mode 100644 static/grid/c4.png create mode 100644 static/grid/c5.png create mode 100644 static/grid/c6.png create mode 100644 static/grid/c7.png create mode 100644 static/grid/c8.png create mode 100644 static/grid/c9.png create mode 100644 static/grid/empty.png create mode 100644 static/h5/download-app/android.png create mode 100644 static/h5/download-app/ios.png create mode 100644 static/h5/download-app/openImg.png create mode 100644 static/limeClipper/photo.svg create mode 100644 static/limeClipper/rotate.svg create mode 100644 static/login-index/apple.png create mode 100644 static/login-index/weixin.png create mode 100644 static/logo.png create mode 100644 static/tabbar/grid.png create mode 100644 static/tabbar/grid_active.png create mode 100644 static/tabbar/im-contacts.png create mode 100644 static/tabbar/im-contacts_active.png create mode 100644 static/tabbar/list.png create mode 100644 static/tabbar/list_active.png create mode 100644 static/tabbar/me.png create mode 100644 static/tabbar/me_active.png create mode 100644 static/uni-center/defaultAvatarUrl.png create mode 100644 static/uni-center/grey.png create mode 100644 static/uni-center/headers.png create mode 100644 static/uni-load-state/disconnection.png create mode 100644 static/uni-quick-login/apple.png create mode 100644 static/uni-quick-login/sms.png create mode 100644 static/uni-quick-login/univerify.png create mode 100644 static/uni-quick-login/user.png create mode 100644 static/uni-quick-login/wechat.png create mode 100644 static/uni-sign-in/background.png create mode 100644 static/uni.ttf create mode 100644 store/index.js create mode 100644 store/modules/user.js create mode 100644 uni-starter.config.js create mode 100644 uni.scss create mode 100644 uniCloud-aliyun/cloudfunctions/uni-analyse-searchhot/index.js create mode 100644 uniCloud-aliyun/cloudfunctions/uni-analyse-searchhot/package.json create mode 100644 "uniCloud-aliyun/database/JQL\346\225\260\346\215\256\345\272\223\347\256\241\347\220\206.jql" create mode 100644 "uniCloud-aliyun/database/JQL\346\237\245\350\257\242.jql" create mode 100644 uniCloud-aliyun/database/db_init.json create mode 100644 uniCloud-aliyun/database/default.jql create mode 100644 uniCloud-aliyun/database/guestbook.schema.json create mode 100644 uniCloud-aliyun/database/opendb-admin-menus.schema.json create mode 100644 uniCloud-aliyun/database/opendb-app-versions.schema.json create mode 100644 uniCloud-aliyun/database/opendb-banner.schema.json create mode 100644 uniCloud-aliyun/database/opendb-department.schema.json create mode 100644 uniCloud-aliyun/database/opendb-mall-goods.schema.json create mode 100644 uniCloud-aliyun/database/opendb-news-articles-detail.schema.json create mode 100644 uniCloud-aliyun/database/opendb-news-articles.schema.json create mode 100644 uniCloud-aliyun/database/opendb-news-categories.schema.json create mode 100644 uniCloud-aliyun/database/opendb-news-comments.schema.json create mode 100644 uniCloud-aliyun/database/opendb-news-favorite.schema.json create mode 100644 uniCloud-aliyun/database/opendb-search-hot.schema.json create mode 100644 uniCloud-aliyun/database/opendb-search-log.schema.json create mode 100644 uniCloud-aliyun/database/opendb-verify-codes.schema.json create mode 100644 uniCloud-aliyun/database/read-news-log.schema.json create mode 100644 uniCloud-aliyun/database/uni-id-device.schema.json create mode 100644 uniCloud-aliyun/database/uni-id-log.schema.json create mode 100644 uniCloud-aliyun/database/uni-id-permissions.schema.json create mode 100644 uniCloud-aliyun/database/uni-id-roles.schema.json create mode 100644 uniCloud-aliyun/database/uni-id-scores.schema.json create mode 100644 uniCloud-aliyun/database/uni-id-users.schema.json create mode 100644 uni_modules.config.json create mode 100644 uni_modules/json-gps/changelog.md create mode 100644 uni_modules/json-gps/js_sdk/gps.js create mode 100644 uni_modules/json-gps/js_sdk/wa-permission/permission.js create mode 100644 uni_modules/json-gps/package.json create mode 100644 uni_modules/json-gps/readme.md create mode 100644 uni_modules/json-interceptor-chooseImage/changelog.md create mode 100644 uni_modules/json-interceptor-chooseImage/js_sdk/main.js create mode 100644 uni_modules/json-interceptor-chooseImage/package.json create mode 100644 uni_modules/json-interceptor-chooseImage/readme.md create mode 100644 uni_modules/uni-badge/changelog.md create mode 100644 uni_modules/uni-badge/components/uni-badge/uni-badge.vue create mode 100644 uni_modules/uni-badge/package.json create mode 100644 uni_modules/uni-badge/readme.md create mode 100644 uni_modules/uni-calendar/changelog.md create mode 100644 uni_modules/uni-calendar/components/uni-calendar/calendar.js create mode 100644 uni_modules/uni-calendar/components/uni-calendar/i18n/en.json create mode 100644 uni_modules/uni-calendar/components/uni-calendar/i18n/index.js create mode 100644 uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json create mode 100644 uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json create mode 100644 uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue create mode 100644 uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue create mode 100644 uni_modules/uni-calendar/components/uni-calendar/util.js create mode 100644 uni_modules/uni-calendar/package.json create mode 100644 uni_modules/uni-calendar/readme.md create mode 100644 uni_modules/uni-captcha/changelog.md create mode 100644 uni_modules/uni-captcha/package.json create mode 100644 uni_modules/uni-captcha/readme.md create mode 100644 uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha/LICENSE.md create mode 100644 uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha/index.js create mode 100644 uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha/package.json create mode 100644 uni_modules/uni-card/changelog.md create mode 100644 uni_modules/uni-card/components/uni-card/uni-card.vue create mode 100644 uni_modules/uni-card/package.json create mode 100644 uni_modules/uni-card/readme.md create mode 100644 uni_modules/uni-collapse/changelog.md create mode 100644 uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue create mode 100644 uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue create mode 100644 uni_modules/uni-collapse/package.json create mode 100644 uni_modules/uni-collapse/readme.md create mode 100644 uni_modules/uni-combox/changelog.md create mode 100644 uni_modules/uni-combox/components/uni-combox/uni-combox.vue create mode 100644 uni_modules/uni-combox/package.json create mode 100644 uni_modules/uni-combox/readme.md create mode 100644 uni_modules/uni-config-center/changelog.md create mode 100644 uni_modules/uni-config-center/package.json create mode 100644 uni_modules/uni-config-center/readme.md create mode 100644 uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/index.js create mode 100644 uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/package.json create mode 100644 uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json create mode 100644 uni_modules/uni-countdown/changelog.md create mode 100644 uni_modules/uni-countdown/components/uni-countdown/i18n/en.json create mode 100644 uni_modules/uni-countdown/components/uni-countdown/i18n/index.js create mode 100644 uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hans.json create mode 100644 uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hant.json create mode 100644 uni_modules/uni-countdown/components/uni-countdown/uni-countdown.vue create mode 100644 uni_modules/uni-countdown/package.json create mode 100644 uni_modules/uni-countdown/readme.md create mode 100644 uni_modules/uni-data-checkbox/changelog.md create mode 100644 uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue create mode 100644 uni_modules/uni-data-checkbox/package.json create mode 100644 uni_modules/uni-data-checkbox/readme.md create mode 100644 uni_modules/uni-data-picker/changelog.md create mode 100644 uni_modules/uni-data-picker/components/uni-data-picker/config.json create mode 100644 uni_modules/uni-data-picker/components/uni-data-picker/keypress.js create mode 100644 uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue create mode 100644 uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js create mode 100644 uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue create mode 100644 uni_modules/uni-data-picker/package.json create mode 100644 uni_modules/uni-data-picker/readme.md create mode 100644 uni_modules/uni-dateformat/changelog.md create mode 100644 uni_modules/uni-dateformat/components/uni-dateformat/date-format.js create mode 100644 uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue create mode 100644 uni_modules/uni-dateformat/package.json create mode 100644 uni_modules/uni-dateformat/readme.md create mode 100644 uni_modules/uni-datetime-picker/changelog.md create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.js create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/keypress.js create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue create mode 100644 uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js create mode 100644 uni_modules/uni-datetime-picker/package.json create mode 100644 uni_modules/uni-datetime-picker/readme.md create mode 100644 uni_modules/uni-drawer/changelog.md create mode 100644 uni_modules/uni-drawer/components/uni-drawer/keypress.js create mode 100644 uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue create mode 100644 uni_modules/uni-drawer/package.json create mode 100644 uni_modules/uni-drawer/readme.md create mode 100644 uni_modules/uni-easyinput/changelog.md create mode 100644 uni_modules/uni-easyinput/components/uni-easyinput/common.js create mode 100644 uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue create mode 100644 uni_modules/uni-easyinput/package.json create mode 100644 uni_modules/uni-easyinput/readme.md create mode 100644 uni_modules/uni-fab/changelog.md create mode 100644 uni_modules/uni-fab/components/uni-fab/uni-fab.vue create mode 100644 uni_modules/uni-fab/components/uni-fab/uni-fab.vue.bak create mode 100644 uni_modules/uni-fab/package.json create mode 100644 uni_modules/uni-fab/readme.md create mode 100644 uni_modules/uni-fav/changelog.md create mode 100644 uni_modules/uni-fav/components/uni-fav/i18n/en.json create mode 100644 uni_modules/uni-fav/components/uni-fav/i18n/index.js create mode 100644 uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json create mode 100644 uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json create mode 100644 uni_modules/uni-fav/components/uni-fav/uni-fav.vue create mode 100644 uni_modules/uni-fav/package.json create mode 100644 uni_modules/uni-fav/readme.md create mode 100644 uni_modules/uni-feedback/changelog.md create mode 100644 uni_modules/uni-feedback/js_sdk/validator/opendb-feedback.js create mode 100644 uni_modules/uni-feedback/package.json create mode 100644 uni_modules/uni-feedback/pages/opendb-feedback/detail.vue create mode 100644 uni_modules/uni-feedback/pages/opendb-feedback/edit.vue create mode 100644 uni_modules/uni-feedback/pages/opendb-feedback/list.vue create mode 100644 uni_modules/uni-feedback/pages/opendb-feedback/opendb-feedback.vue create mode 100644 uni_modules/uni-feedback/readme.md create mode 100644 uni_modules/uni-feedback/uniCloud/database/opendb-feedback.schema.json create mode 100644 uni_modules/uni-file-picker/changelog.md create mode 100644 uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js create mode 100644 uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue create mode 100644 uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue create mode 100644 uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue create mode 100644 uni_modules/uni-file-picker/components/uni-file-picker/utils.js create mode 100644 uni_modules/uni-file-picker/package.json create mode 100644 uni_modules/uni-file-picker/readme.md create mode 100644 uni_modules/uni-forms/changelog.md create mode 100644 uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue create mode 100644 uni_modules/uni-forms/components/uni-forms/uni-forms.vue create mode 100644 uni_modules/uni-forms/components/uni-forms/validate.js create mode 100644 uni_modules/uni-forms/package.json create mode 100644 uni_modules/uni-forms/readme.md create mode 100644 uni_modules/uni-goods-nav/changelog.md create mode 100644 uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json create mode 100644 uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js create mode 100644 uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json create mode 100644 uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json create mode 100644 uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue create mode 100644 uni_modules/uni-goods-nav/package.json create mode 100644 uni_modules/uni-goods-nav/readme.md create mode 100644 uni_modules/uni-grid/changelog.md create mode 100644 uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue create mode 100644 uni_modules/uni-grid/components/uni-grid/uni-grid.vue create mode 100644 uni_modules/uni-grid/package.json create mode 100644 uni_modules/uni-grid/readme.md create mode 100644 uni_modules/uni-group/changelog.md create mode 100644 uni_modules/uni-group/components/uni-group/uni-group.vue create mode 100644 uni_modules/uni-group/package.json create mode 100644 uni_modules/uni-group/readme.md create mode 100644 uni_modules/uni-icons/changelog.md create mode 100644 uni_modules/uni-icons/components/uni-icons/icons.js create mode 100644 uni_modules/uni-icons/components/uni-icons/uni-icons.vue create mode 100644 uni_modules/uni-icons/components/uni-icons/uni.ttf create mode 100644 uni_modules/uni-icons/package.json create mode 100644 uni_modules/uni-icons/readme.md create mode 100644 uni_modules/uni-id-cf/changelog.md create mode 100644 uni_modules/uni-id-cf/package.json create mode 100644 uni_modules/uni-id-cf/readme.md create mode 100644 uni_modules/uni-id-cf/uniCloud/cloudfunctions/uni-id-cf/index.js create mode 100644 uni_modules/uni-id-cf/uniCloud/cloudfunctions/uni-id-cf/package.json create mode 100644 uni_modules/uni-id/changelog.md create mode 100644 uni_modules/uni-id/package.json create mode 100644 uni_modules/uni-id/readme.md create mode 100644 uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/LICENSE.md create mode 100644 uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/index.js create mode 100644 uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/package.json create mode 100644 uni_modules/uni-image-menu/changelog.md create mode 100644 uni_modules/uni-image-menu/js_sdk/uni-image-menu.js create mode 100644 uni_modules/uni-image-menu/package.json create mode 100644 uni_modules/uni-image-menu/readme.md create mode 100644 uni_modules/uni-indexed-list/changelog.md create mode 100644 uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue create mode 100644 uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue create mode 100644 uni_modules/uni-indexed-list/package.json create mode 100644 uni_modules/uni-indexed-list/readme.md create mode 100644 uni_modules/uni-link/changelog.md create mode 100644 uni_modules/uni-link/components/uni-link/uni-link.vue create mode 100644 uni_modules/uni-link/package.json create mode 100644 uni_modules/uni-link/readme.md create mode 100644 uni_modules/uni-list/changelog.md create mode 100644 uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue create mode 100644 uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss create mode 100644 uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue create mode 100644 uni_modules/uni-list/components/uni-list-item/uni-list-item.vue create mode 100644 uni_modules/uni-list/components/uni-list/uni-list.vue create mode 100644 uni_modules/uni-list/components/uni-list/uni-refresh.vue create mode 100644 uni_modules/uni-list/components/uni-list/uni-refresh.wxs create mode 100644 uni_modules/uni-list/package.json create mode 100644 uni_modules/uni-list/readme.md create mode 100644 uni_modules/uni-load-more/changelog.md create mode 100644 uni_modules/uni-load-more/components/uni-load-more/i18n/en.json create mode 100644 uni_modules/uni-load-more/components/uni-load-more/i18n/index.js create mode 100644 uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json create mode 100644 uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json create mode 100644 uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue create mode 100644 uni_modules/uni-load-more/package.json create mode 100644 uni_modules/uni-load-more/readme.md create mode 100644 uni_modules/uni-nav-bar/changelog.md create mode 100644 uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue create mode 100644 uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue create mode 100644 uni_modules/uni-nav-bar/package.json create mode 100644 uni_modules/uni-nav-bar/readme.md create mode 100644 uni_modules/uni-notice-bar/changelog.md create mode 100644 uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue create mode 100644 uni_modules/uni-notice-bar/package.json create mode 100644 uni_modules/uni-notice-bar/readme.md create mode 100644 uni_modules/uni-number-box/changelog.md create mode 100644 uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue create mode 100644 uni_modules/uni-number-box/package.json create mode 100644 uni_modules/uni-number-box/readme.md create mode 100644 uni_modules/uni-pagination/changelog.md create mode 100644 uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue create mode 100644 uni_modules/uni-pagination/package.json create mode 100644 uni_modules/uni-pagination/readme.md create mode 100644 uni_modules/uni-popup/changelog.md create mode 100644 uni_modules/uni-popup/components/uni-popup-dialog/keypress.js create mode 100644 uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue create mode 100644 uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue create mode 100644 uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue create mode 100644 uni_modules/uni-popup/components/uni-popup/i18n/en.json create mode 100644 uni_modules/uni-popup/components/uni-popup/i18n/index.js create mode 100644 uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json create mode 100644 uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json create mode 100644 uni_modules/uni-popup/components/uni-popup/keypress.js create mode 100644 uni_modules/uni-popup/components/uni-popup/message.js create mode 100644 uni_modules/uni-popup/components/uni-popup/popup.js create mode 100644 uni_modules/uni-popup/components/uni-popup/share.js create mode 100644 uni_modules/uni-popup/components/uni-popup/uni-popup.vue create mode 100644 uni_modules/uni-popup/package.json create mode 100644 uni_modules/uni-popup/readme.md create mode 100644 uni_modules/uni-rate/changelog.md create mode 100644 uni_modules/uni-rate/components/uni-rate/uni-rate.vue create mode 100644 uni_modules/uni-rate/package.json create mode 100644 uni_modules/uni-rate/readme.md create mode 100644 uni_modules/uni-row/changelog.md create mode 100644 uni_modules/uni-row/components/uni-col/uni-col.vue create mode 100644 uni_modules/uni-row/components/uni-row/uni-row.vue create mode 100644 uni_modules/uni-row/package.json create mode 100644 uni_modules/uni-row/readme.md create mode 100644 uni_modules/uni-search-bar/changelog.md create mode 100644 uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json create mode 100644 uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js create mode 100644 uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json create mode 100644 uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json create mode 100644 uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue create mode 100644 uni_modules/uni-search-bar/package.json create mode 100644 uni_modules/uni-search-bar/readme.md create mode 100644 uni_modules/uni-segmented-control/changelog.md create mode 100644 uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue create mode 100644 uni_modules/uni-segmented-control/package.json create mode 100644 uni_modules/uni-segmented-control/readme.md create mode 100644 uni_modules/uni-share/changelog.md create mode 100644 uni_modules/uni-share/js_sdk/uni-image-menu.js create mode 100644 uni_modules/uni-share/js_sdk/uni-share.js create mode 100644 uni_modules/uni-share/package.json create mode 100644 uni_modules/uni-share/readme.md create mode 100644 uni_modules/uni-sign-in/changelog.md create mode 100644 uni_modules/uni-sign-in/components/uni-sign-in/uni-sign-in.vue create mode 100644 uni_modules/uni-sign-in/package.json create mode 100644 uni_modules/uni-sign-in/pages/demo/demo.vue create mode 100644 uni_modules/uni-sign-in/readme.md create mode 100644 uni_modules/uni-sign-in/uniCloud/cloudfunctions/uni-clientDB-actions/signIn.js create mode 100644 uni_modules/uni-sign-in/uniCloud/database/opendb-sign-in.schema.json create mode 100644 uni_modules/uni-steps/changelog.md create mode 100644 uni_modules/uni-steps/components/uni-steps/uni-steps.vue create mode 100644 uni_modules/uni-steps/package.json create mode 100644 uni_modules/uni-steps/readme.md create mode 100644 uni_modules/uni-swipe-action/changelog.md create mode 100644 uni_modules/uni-swipe-action/components/uni-swipe-action-item/bindingx.js create mode 100644 uni_modules/uni-swipe-action/components/uni-swipe-action-item/index.wxs create mode 100644 uni_modules/uni-swipe-action/components/uni-swipe-action-item/isPC.js create mode 100644 uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpalipay.js create mode 100644 uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpother.js create mode 100644 uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpwxs.js create mode 100644 uni_modules/uni-swipe-action/components/uni-swipe-action-item/render.js create mode 100644 uni_modules/uni-swipe-action/components/uni-swipe-action-item/uni-swipe-action-item.vue create mode 100644 uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs create mode 100644 uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue create mode 100644 uni_modules/uni-swipe-action/package.json create mode 100644 uni_modules/uni-swipe-action/readme.md create mode 100644 uni_modules/uni-swiper-dot/changelog.md create mode 100644 uni_modules/uni-swiper-dot/components/uni-swiper-dot/uni-swiper-dot.vue create mode 100644 uni_modules/uni-swiper-dot/package.json create mode 100644 uni_modules/uni-swiper-dot/readme.md create mode 100644 uni_modules/uni-table/changelog.md create mode 100644 uni_modules/uni-table/components/uni-table/uni-table.vue create mode 100644 uni_modules/uni-table/components/uni-tbody/uni-tbody.vue create mode 100644 uni_modules/uni-table/components/uni-td/uni-td.vue create mode 100644 uni_modules/uni-table/components/uni-th/filter-dropdown.vue create mode 100644 uni_modules/uni-table/components/uni-th/uni-th.vue create mode 100644 uni_modules/uni-table/components/uni-thead/uni-thead.vue create mode 100644 uni_modules/uni-table/components/uni-tr/table-checkbox.vue create mode 100644 uni_modules/uni-table/components/uni-tr/uni-tr.vue create mode 100644 uni_modules/uni-table/package.json create mode 100644 uni_modules/uni-table/readme.md create mode 100644 uni_modules/uni-tag/changelog.md create mode 100644 uni_modules/uni-tag/components/uni-tag/uni-tag.vue create mode 100644 uni_modules/uni-tag/package.json create mode 100644 uni_modules/uni-tag/readme.md create mode 100644 uni_modules/uni-title/changelog.md create mode 100644 uni_modules/uni-title/components/uni-title/uni-title.vue create mode 100644 uni_modules/uni-title/package.json create mode 100644 uni_modules/uni-title/readme.md create mode 100644 uni_modules/uni-transition/changelog.md create mode 100644 uni_modules/uni-transition/components/uni-transition/createAnimation.js create mode 100644 uni_modules/uni-transition/components/uni-transition/uni-transition.vue create mode 100644 uni_modules/uni-transition/package.json create mode 100644 uni_modules/uni-transition/readme.md create mode 100644 uni_modules/uni-ui/changelog.md create mode 100644 uni_modules/uni-ui/components/uni-ui/uni-ui.vue create mode 100644 uni_modules/uni-ui/package.json create mode 100644 uni_modules/uni-ui/readme.md create mode 100644 uni_modules/uni-upgrade-center-app/changelog.md create mode 100644 uni_modules/uni-upgrade-center-app/package.json create mode 100644 uni_modules/uni-upgrade-center-app/pages/upgrade-popup.vue create mode 100644 uni_modules/uni-upgrade-center-app/pages_init.json create mode 100644 uni_modules/uni-upgrade-center-app/readme.md create mode 100644 uni_modules/uni-upgrade-center-app/static/app_update_close.png create mode 100644 uni_modules/uni-upgrade-center-app/static/bg_top.png create mode 100644 uni_modules/uni-upgrade-center-app/static/button_bg.png create mode 100644 uni_modules/uni-upgrade-center-app/uniCloud/cloudfunctions/check-version/check-version.param.json create mode 100644 uni_modules/uni-upgrade-center-app/uniCloud/cloudfunctions/check-version/index.js create mode 100644 uni_modules/uni-upgrade-center-app/utils/call-check-version.js create mode 100644 uni_modules/uni-upgrade-center-app/utils/check-update.js create mode 100644 uni_modules/uni-upgrade-center/uniCloud/database/opendb-app-list.schema.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..78df135 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +unpackage/ +.hbuilderx +node_modules +.DS_Store +uni_modules_tools/copy +package-lock.json \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..d680f3a --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +unpackage/ +.hbuilderx +node_modules +.DS_Store +uni_modules_tools/copy +package-lock.json +uni_modules_tools +uni_modules.config.json \ No newline at end of file diff --git a/App.vue b/App.vue new file mode 100644 index 0000000..f112766 --- /dev/null +++ b/App.vue @@ -0,0 +1,57 @@ + + + diff --git a/LICENSE b/LICENSE index 4470915..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2021 崔红保 - -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. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.en.md b/README.en.md deleted file mode 100644 index d2b51ef..0000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# base-app - -#### Description -{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**} - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/README.md b/README.md index 75b7769..0dfbde6 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,567 @@ -# base-app +#### 视频介绍 + + 腾讯课堂uniCloud视频教程 + -#### 介绍 -{**以下是 Gitee 平台说明,您可以替换此简介** -Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台 -无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)} +注:视频会有滞后问题,入门为主。最新的完整功能请看以下文档 -#### 软件架构 -软件架构说明 +#### 简介 +uni-starter是一个集成了大量商用项目常见功能的,云端一体应用快速开发基本项目模版。 +APP有很多通用的功能,比如登录注册、头像、设置、banner、... uni-starter将这些功能都已经集成好。 -#### 安装教程 +直接在`hbuilderx`新建项目选择`uni-starter`模板,即可在此基础上快速开发自己的特色业务。 -1. xxxx -2. xxxx -3. xxxx +有了`uni-starter`,再加上`schema2code`生成前端页面,一个简单应用就可以快速完成。 -#### 使用说明 +如果说[uniCloud admin](https://uniapp.dcloud.io/uniCloud/admin)是管理端项目的基本项目模版,那么uni-starter则是用户端、尤其是移动端的基础项目模板。 -1. xxxx -2. xxxx -3. xxxx +`uni-starter` + `uniCloud admin` 提供了用户端和管理端的基本项目模版,应用开发从未如此简单快捷! -#### 参与贡献 +##### 扫码体验:h5版演示效果(链接:[https://uni-starter.dcloud.net.cn](https://uni-starter.dcloud.net.cn)) + -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +### uni-starter集成包括: +1. 用户管理: + - 登录注册(用户名密码登录、手机号验证码登录、APP一键登录、微信登录、Apple登录、微信小程序登录) + - 修改密码、忘记密码、头像更换(集成图片裁剪)、昵称修改、积分查看、退出登录 +2. 系统设置: + - App更新(整包升级、wgt升级、强制升级,后台搭配uniCloud admin的升级中心插件管理) + - 推送开关(app)、清除缓存(app) + - 指纹解锁(app)、人脸解锁(app) + - 多语言切换 + - 账号注销(正在完善中...) +3. 隐私权限:内置Android先弹出隐私协议对话框,然后再向用户申请设备权限 +4. 权限引导:当应用拒绝授权某些权限,但在后续使用中又需要这个权限;此时实现:引导用户可“一键跳转至系统设置”中开启。 + - 而不是报错让用户自己去找解决方案(更好的用户体验)。 + - 采用高内聚低耦合的设计结构,直接在应用启动时,应用拦截器中实现。免去在每个业务代码中处理这类问题,更优雅更方便。 + - 已实现项目:摄像头、相册、获取GPS定位、网络2/3/4/5G和Wi-Fi。你可以参考这些实现,处理更多该类场景的处理。uni-starter也会持续更新完善。 +5. 实用功能 + - 问题与反馈、关于、隐私政策、用户服务协议 + - banner(后台搭配uniCloud admin的banner插件管理) + - 新闻的搜索、列表、详情、分享。通过clientDB实现,开发者直接修改定义的表名等参数,即可轻松改为自己的业务 + - 可覆盖原生层的分享菜单 + - h5版在页面顶部(全局悬浮)引导用户点击下载App + - 营销裂变:点击“分销推荐”,生成带用户inviteCode参数的应用下载页(H5),一键分享到微信或微信朋友圈等。被邀请人打开下载页面点击下载,设备剪贴板的内容会被自动设置为邀请者的inviteCode。被邀请人下载app之后通过任何方式登录(含:注册并登录),uni-starter框架会自动获取设备剪切板中的inviteCode提交到服务端绑定关联关系。 +6. 更好的性能:首页采用nvue,fast编译模式,加快App端启动速度 +7. 内置拦截器: + - 页面路由拦截,配置需强制登录的页面;打开时自动检测`token`若无效就自动跳转到登录页 + - 调用云函数(callFunction)拦截器,自动携带必要参数、自动处理响应体。详见9.自动完成1-2 +8. 自动完成: + - 分析uniCloud.callfunction和clientDB操作的响应体,判断code执行对应的操作如跳转到登录页,自动续期token + - 操作注册/登录操作自动获取客户端设备:push_clientid、imei、oaid、idfa新增/更新到数据表uni-id-device + - 异常恢复处理:断网恢复后自动重连“因网络错误导致的”网络请求 + - 为迎合苹果App Store的规则,登录与分享功能项显示之前自动检测是否安装了对应客户端。比如:设备未安装微信则不显示微信快捷登录和微信分享选项 +* 更多功能模块会不断更新,请持续关注本插件 -#### 特技 +## 快速体验部署流程 +#### 1. 开通uniCloud +- 开通`uniCloud`:本项目是云端一体的,它的云端代码需要部署在uniCloud云服务空间里,需要开通uniCloud。在[https://unicloud.dcloud.net.cn/](https://unicloud.dcloud.net.cn/)登录,按云厂商要求进行实名认证。 +- 在uniCloud认证通过后,创建一个服务空间给本项目使用。选择阿里云或腾讯云均可,两种服务空间差异[详情](https://uniapp.dcloud.net.cn/uniCloud/price) -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) +#### 2. 运行云服务空间初始化向导 + + + + +## 功能模块介绍 +### 1.登录模块 +|登录类型 |描述 | +|-- |-- | +|smsCode |验证码登录 | +|univerify |读取手机SIM卡一键登录 | +|username |账号密码登录 | +|weixin |微信登录 | +|apple |苹果登录 | + +配置文件:`项目根目录/uni-starter.config.js` +```js +{ + "router":{ + "login":["username","smsCode"] + } +} +``` + +#### 启用登录方式 +如上示例配置为:`["username","smsCode"]` 表示启用:验证码登录、账号密码登录。 + +同理配置为:`["weixin","username","smsCode"]` 则表示启用:微信登录、验证码登录、账号密码登录。 + +总结:需要几项列举几项即可。 + +#### 优先级 +在uni-starter框架中执行`uni.navigateTo({url: "/pages/ucenter/login-page/index/index"})`,会根据配置跳转到相应的登录页面。如果配置内容为:`["username","smsCode"]`会自动切换到"配置的第0项,也就是`username`类型的登录方式对应的页面”,即`账户登录`方式页面,路径:`/pages/ucenter/login-page/pwd-login/pwd-login` + +#### 平台差异性配置 +这里支持用[条件编译](https://uniapp.dcloud.io/platform?id=%e6%9d%a1%e4%bb%b6%e7%bc%96%e8%af%91)因此你可以配置在不同平台下拥有的登录方式。 +如下配置,即表示仅在APP端启用“短信验证码登录” +```js +"login": [ + "username","univerify","weixin","apple" + // #ifdef APP-PLUS + "smsCode", + // #endif +] +``` + +#### 生效策略 +登录方式有如上5种,虽然你希望有几种登录方式就在配置中列举几种。但是有的登录方式可能因为设备环境问题而不被支持; +比如你正确地配置了微信登录,而用户的手机并没有安装微信,这样微信登录功能就无法使用。 +并且如果出现这种情况你的app会被iOS的App Store拒绝上架。 +所以在这里,我们的生效策略在检测:你是否有列举到某个配置项为前提的情况下,增加了检测当前环境是否支持,如果不支持会自动隐藏。 + +#### 在uni-app框架中配置: +在应用模块:`manifest.json` App模块配置 --> OAuth(登录鉴权)--> 勾选并配置你所需要的模块 ++ 一键登录: + [开通配置](https://dev.dcloud.net.cn/uniLogin) + [使用指南](https://uniapp.dcloud.io/univerify) ++ [苹果登录集成指南](https://ask.dcloud.net.cn/article/36651) ++ 短信登录: + 使用本功能需要在[DCloud开发者中心](https://dev.dcloud.net.cn/uniSms)开通并充值 + 教程参考[短信服务开通指南](https://ask.dcloud.net.cn/article/37534) + 修改短信注册/登录发生验证码的模板id,在文件`/uniCloud-aliyun/cloudfunctions/uni-id-cf/index.js` 搜索 `const templateId = '11753'` 替换为自己申请的模板id + +#### 服务端配置 +uni-starter服务端使用[uni-config-center](https://ext.dcloud.net.cn/plugin?id=4425)统一管理这些配置, +文件路径`/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json` +详情下文[目录结构](#id=catalogue) 和[uni-id配置说明](https://uniapp.dcloud.io/uniCloud/uni-id?id=configjson%e7%9a%84%e8%af%b4%e6%98%8e) + +### 2.路由拦截 +#### 应用场景 +有些页面,限允许已经登录后用户才访问。 +常规的做法是打开这类页面之前,检查(前端校验)uni_id_token的值是否有效,如果无效会自动跳转到登录页面。 +而这样的页面有很多,入口也不少。面向过程的写法会产生大量的代码冗余,且不易维护。 +而uni-starter基于拦截器(`uni.addInterceptor`),提供了仅需简单配置即可实现的路由拦截功能。 +#### 配置方式 +支持两种模式(二选一) +##### 黑名单模式 +列举需要强制登录的页面完整路径(支持正则) +##### 白名单模式 +列举不需要强制登录即可访问的页面完整路径(支持正则) +#### 配置示例 +配置文件:`项目根目录/uni-starter.config.js` + +```js +"router": { + "needLogin" : [ + {pattern:/^\/pages\/list.*/}, //支持正则表达式 + "/uni_modules/uni-news-favorite/pages/uni-news-favorite/list", + "/uni_modules/uni-feedback/pages/uni-feedback/add" + ], +/* + 请注意上下,黑名单(needLogin)、白名单(visitor)两种配置模式二选一不可同时使用 +*/ + "visitor" : [ + "/",//注意入口页必须直接写 "/" + {"pattern":/^\/pages\/list.*/}, //支持正则表达式 + {"pattern":/^\/pages\/ucenter\/login-page.*/}, + "/pages/common/webview/webview", + "/pages/grid/grid", + "/pages/ucenter/ucenter", + "/pages/ucenter/guestbook/guestbook", + "/pages/ucenter/about/about", + "/pages/ucenter/settings/settings" + ] +} +``` + +##### 优势: +传统的路由管理方式是对uni-app框架路由写法的二次封装,自定义的写法不支持ide的代码提示,且不优雅。 +另外不同插件作者封装不同的路由管理方式,这样做出来的插件与用户的项目结合时,路由写法不统一的差异需要去磨平。 +为此`uni-starter`基于`uni.addInterceptor`(拦截器)实现路由管理。 + +##### 注意: +- uni-starter的路由拦截,仅在调用路由API(navigateTo、redirectTo、reLaunch、switchTab)时触发。应用的首页是由系统自动打开,不会触发拦截器。首页需要强制登录才能访问的场景,不由路由控制。但不用担心,如果未登录的用户,打开了需要登录才能访问页面,必定会触发需要携带有效token才能访问的API。此时则会返回相应的响应体,uni-starter监测到token无效这类的响应体也会自动跳转到登录页(这种效果需要前后端都开发完成才体验到)。 +- uni-starter框架不能将登录页`/pages/ucenter/login-page/index/index`设置为首页,否则由拦截器实现的路由管理将生效。 +- 拦截器实现的路由控制,是在路由跳转未完成之前触发。路由切换方式(navigateTo、redirectTo、reLaunch、switchTab)URL参数必须使用绝对路径路 + +### 3.h5版在页面顶部引导用户`点击下载App` +把h5端用户引流到APP端,是一个非常实用的功能。相对于h5,APP端有更高的用户留存和更好的产品体验。 +uni-starter集成了这个功能,你只需直接在`项目根目录/uni-starter.config.js`的"h5"->"openApp"中配置相关内容,即可开启全局悬浮的下载引导。 +这也是一个演示开发者如何在h5端做全局悬浮块的例子。你也可以在`/common/openApp.js`中修改他的样式等代码等,注意他只支持原生js语法。 + +### 4.分享模块 +一个可覆盖原生层分享模块 +- 应用配置:`manifest.json` App模块配置 --> Share --> 勾选并配置你所需要的模块 +- 分享功能配置参数,随着应用的业务场景决定,在各场景调用的时候配置。参考uni-starter的`/pages/list/detail.vue`的`methods -> shareClick` +- 更多`uni-share`的介绍 [详情](https://ext.dcloud.net.cn/plugin?id=4860) + +### 5.升级中心相关 +为了解决开发者维护多个 App 升级繁琐,重复逻辑过多,管理不便的问题,升级中心`uni-upgrade-center`应运而生。 +提供了简单、易用、统一的 App 管理、App 版本管理、安装包发布管理,升级检测更新管理。 +- 升级中心分为两个部分:`uni-upgrade-center` Admin管理后台和`uni-upgrade-center - Admin`前台检测更新。 +- `uni-upgrade-center`的介绍 [详情](https://ext.dcloud.net.cn/plugin?id=4542) +- `uni-upgrade-center - Admin`的介绍 [详情](https://ext.dcloud.net.cn/plugin?id=4470) + +### 6.意见反馈 +- 客户端[详情](https://ext.dcloud.net.cn/plugin?id=50) +- admin端[详情](https://ext.dcloud.net.cn/plugin?id=4992) + +### 7.指纹识别模块 +- `manifest.json` App模块配置 --> `Fingerprint`指纹识别 + +### 8.消息推送模块 +- `manifest.json` App模块配置 --> `push`消息推送 + +### 9.隐私政策弹框 +根据工业和信息化部关于开展APP侵害用户权益专项整治要求,App提交到应用市场必须满足以下条件: +- 应用启动运行时需弹出隐私政策协议,说明应用采集用户数据 +- 应用不能强制要求用户授予权限,即不能“不给权限不让用” ++ 如不希望应用启动时申请“读写手机存储”和“访问设备信息”权限,请参考:https://ask.dcloud.net.cn/article/36549 + +配置弹出“隐私协议和政策”打开项目的manifest.json文件,切换到“源码视图”项 +在`manifest.json` -> `app-plus` -> `privacy` 节点下添加 prompt节点 +```js +"privacy" : { + "prompt" : "template", + "template" : { + "title" : "服务协议和隐私政策", + "message" : "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。
  你可阅读《服务协议》《隐私政策》了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。", + "buttonAccept" : "同意", + "buttonRefuse" : "暂不同意" + } +} +``` +- prompt + 字符串类型,必填,隐私政策提示框配置策略,可取值template、custom、none,默认值为none + + template + 使用原生提示框模板,可自定义标题、内容已经按钮上的文本 + + custom + 自定义隐私政策提示框,uni-app项目中推荐使用nvue页面进行自定义,5+ APP使用html页面进行自定义 + + none + 不弹出隐私政策提示框 +- template + json格式,可选,模板提示框上显示的内容 + + title + 模板提示框上的标题,默认为“服务协议和隐私政策” + + message + 模板提示框上的内容,richtext类型字符串,支持a/font/br等节点,点击a链接会调用内置页面打开其href属性中链接地址。 + **注意:务必配置此提示内容,或参考上面示例内容并修改《服务协议》和《隐私政策》链接地址** + + buttonAccept + 模板提示框上接受按钮的文本,默认值为“我知道了” + + buttonRefuse + 模板提示框上拒绝按钮的文本,默认不显示此按钮 + + second + HBuilderX3.1.12+版本新增支持隐私提示框二次确认提示,用于配置二次确认提示框显示内容,message属性值不为空时弹出二次确认提示框 + + title 二次确认提示框上的标题 + + message 二次确认提示框上的内容,支持richtext类型字符串 + + buttonAccept 二次确认提示框上接受按钮的文本 + + buttonRefuse 二次确认提示框上拒绝按钮的文本 + +> 更多Android平台隐私与政策提示框配置方法,[详情](https://ask.dcloud.net.cn/article/36937) + +##### 注意: +1. 最新的华为应用市场要求,隐私政策提示框上接受按钮的文本,必须为“同意”而不能是其他有歧义的文字。 +2. 配置后提交云端打包后生效。理论上绝大部分和`manifest.json`生效相关的配置均需要提交云打包后生效 + +### 10.拦截器改造后的uniCloud +1. Debug,调试期间开启Debug。接口一旦fail就会弹出真实错误信息。否则将弹出,系统错误请稍后再试! +```js + if(Debug){ + console.log(e); + uni.showModal({ + content: JSON.stringify(e), + showCancel: false + }); + } +``` +2. 断网自动重试,当callFunction为fail时检测是否因断网引起。如果是会提醒用户并且会在恢复网络之后自动重新发起请求 +3. 常规的errCoder自动执行对应程序,如token无效/过期自动跳转到登录页面。 +4. token自动续期。 + +### 11.举例路由控制原理,深入了解拦截器的使用 +比如你希望在打开用户中心等页面之前,都检查一下该用户是否登录,否则就重定向到登录页面。使用拦截器你可以用以下写法在应用入口定义全局生效: + +```js + //定义各个页面,这里为了演示uni-starter框架是把该定义写在全局配置uni-starter.config.js中 + let needLogin = ["/pages/ucenter/userinfo/userinfo", ... ] + + uni.addInterceptor("navigateTo", { + invoke(e) { // 调用前拦截 + //获取用户的token + const token = uni.getStorageSync('uni_id_token') + //获取当前页面路径(即url去掉"?"和"?"后的参数) + const url = e.url.split('?')[0] + //判断要打开的页面是否需要验证登录 + if (needLogin.includes(url) && token == '') { + uni.showToast({ + title: '该页面需要登录才能访问,请先登录', + icon: 'none' + }) + uni.navigateTo({ + url: "/pages/ucenter/login-page/index/index" + }) + return false + } + }, + fail(err) { // 失败回调拦截 + console.log(err); + }, + }) +``` +- 而路由跳转方法不仅有`uni.navigateTo`还有`uni.redirectTo`,`uni.reLaunch`,`uni.switchTab`; +- 另外我们还希望控制直接跳转至哪种登录类型 +所以在uni-starter框架中我们这样定义: +uni-starter/common/appInit.js 的第228-280行 +```js + const {"router": {needLogin,login} } = uniStarterConfig //需要登录的页面 + let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"]; + list.forEach(item => { //用遍历的方式分别为,uni.navigateTo,uni.redirectTo,uni.reLaunch,uni.switchTab这4个路由方法添加拦截器 + uni.addInterceptor(item, { + invoke(e) { // 调用前拦截 + //获取用户的token + const token = uni.getStorageSync('uni_id_token') + //获取当前页面路径(即url去掉"?"和"?"后的参数) + const url = e.url.split('?')[0] + //判断要打开的页面是否需要验证登录 + if (needLogin.includes(url) && token == '') { + uni.showToast({ + title: '该页面需要登录才能访问,请先登录', + icon: 'none' + }) + uni.navigateTo({ + url: "/pages/ucenter/login-page/index/index" + }) + return false + } + //控制登录优先级 + if (url == '/pages/ucenter/login-page/index/index') { + //一键登录(univerify)、账号(username)、验证码登录(短信smsCode) + if (login[0] == 'univerify') { + if(e.url == url) { e.url += '?' } //添加参数之前判断是否带了`?`号如果没有就补上,因为当开发场景本身有参数的情况下是已经带了`?`号 + e.url += "univerify_first=true" + } else if (login[0] == 'username') { + e.url = "/pages/ucenter/login-page/pwd-login/pwd-login" + } + } + return true + }, + fail(err) { // 失败回调拦截 + console.log(err); + }, + }) + }) +``` + +### 12.关于升级 +- 项目升级 + + uni-starter遵循uni-app的插件模块化规范,即:[uni_modules](https://uniapp.dcloud.io/uni_modules) 。他是个项目类型的插件。在项目的根目录下有一个符合uni_modules规范的package.json文件,在这个文件右键-从插件市场更新即可更新该插件。 + +- 插件升级 + + 非项目类型的uni_modules插件,是项目根目录下的uni_modules目录下。以插件ID为插件文件夹命名,在该目录右键也会看到“从插件市场更新”选项,点击即可更新该插件。 + +### 13.多语言国际化 + uni-starter支持多语言国际化。默认开启,可以在`uni-starter.config.js`->`i18n`->`enable`中配置 + 如果你启用了多语言国际化需要先阅读:[uni-app多语言国际化](https://uniapp.dcloud.io/collocation/i18n?id=%e6%a1%86%e6%9e%b6%e5%86%85%e7%bd%ae%e7%bb%84%e4%bb%b6%e5%92%8capi%e5%9b%bd%e9%99%85%e5%8c%96) + +## 应用启动时序介绍 +文件路径: App.vue +```js + import initApp from '@/common/appInit.js'; + export default { + onLaunch: function() { + initApp(); + } + } +``` +onLaunch生命周期执行了 +1. 全局监听clientDB的err事件, + - 判断是否为token过期失效等需要重新登录的问题。自动跳转到登录页面 + - 检测本地的token是否有效(存在且并未过期)否则跳转到登录页面 +2. 预登录一键登录功能 +3. 执行了initApp()包含以下操作 + 1. 读取uni-starter.config并挂载到globalData的config下 + 2. 读取应用版本号,并存到globalData下 + 3. 检查是否有可更新的应用版本,决定是否启动在线更新版本 + 4. 监听设备的网络变化并以uni.showToast APi的方式提醒用户 + 5. 使用[拦截器](https://uniapp.dcloud.io/api/interceptor?id=addinterceptor) 实现 + - 页面路由拦截,配置需强制登录的页面;打开时检测,如果token无效就自动跳转到登录页 + - 优雅实现:自动引导打开`选择图片`所需要的权限。当调用`uni.chooseImage`时检测到无权限自动开启引导。并不是在每次调用接口时处理这类问题,你可以参考该例子做更多该类场景的处理。uni-starter也会持续完善 + + +## 配置文件 +uni-starter提供了`uni-starter.config.js`,可配置选择登录注册方式及优先级等,可指定该应用是否强制登录才能进入某个页面。配置项内容如下: +```js +module.exports = { + "h5": { + "url": "https://static-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e.bspapp.com", // 前端网页托管的域名 + // 在h5端全局悬浮引导用户下载app的功能 更多自定义要求在/common/openApp.js中修改 + "openApp": { + //点击悬浮下载栏后打开的网页链接 + "openUrl": 'https://sj.qq.com/myapp/detail.htm?apkName=com.tencent.android.qqdownloader&info=6646FD239A6EBA9E2DEE5DFC7E18D867', + //左侧显示的应用名称 + "appname": 'uni-starter', + //应用的图标 + "logo": './static/logo.png', + } + }, + "mp": { + "weixin": { + //微信小程序原始id,微信小程序分享时 + "id": "gh_132465798" + } + }, + "router": { + //配置需要路由拦截的页面地址,在打开这些页面之前会自动检查(无需联网)uni_id_token的值,如果token无效就自动跳转到登录页 + "needLogin": [ + "/pages/ucenter/userinfo/userinfo", + "/uni_modules/uni-news-favorite/pages/uni-news-favorite/list", + ], + "login": ["smsCode","univerify", "username", "weixin", "apple"], + /* + 这里会根据数组的第0项,决定登录方式的第一优先级是哪种登录方式。 + 所有你希望拥有的登录方式这里都需要一一列举,未列举到的或设备环境不支持的登录方式将被隐藏。 + 如果你需要在不同平台有不同的配置,直接用条件编译即可。 + */ + }, + //关于应用 + "about": { + //应用名称 + "appName": "uni-starter", + //应用logo + "logo": "/static/logo.png", + //公司名称 + "company": "数字天堂(北京)网络技术有限公司", + //口号 + "slogan": "为开发而生", + //政策协议 + "agreements": [{ + "title": "用户服务协议", //协议名称 + "url": "https://ask.dcloud.net.cn/protocol.html" //对应的网络链接 + }, + { + "title": "隐私政策", + "url": "https://ask.dcloud.net.cn/protocol.html" + } + ], + //应用的链接,用于分享到第三方平台和生成关于我们页的二维码 + "download": "https://m3w.cn/uniapp" + }, + //用于打开应用市场评分界面 + "marketId":{ + "ios":"id1417078253", + "android":"123456" + }, + //配置多语言国际化。i18n为英文单词 internationalization的首末字符i和n,18为中间的字符数 是“国际化”的简称 + "i18n":{ + "enable":false //默认启用,国际化。如果你不想使用国际化相关功能,请改为false + } +} +``` + +## 目录结构@catalogue +
+uni-starter
+├─uniCloud-aliyun	
+│	├─cloudfunctions 云函数目录
+│	|	├─common 公共模块
+│	│	|	├─uni-config-center		uni-starter的服务端配置中心,项目所有云函数的配置在这里填写 详情
+│	│	|	|	├─index.js			config-center入口文件
+│	│	|	|	└─uni-id			uni-id模块配置目录
+│	│	|	|		├─config.json	uni-id对应的配置数据:微信登录、一键登录、短信验证码登录等key都在这里填写详情
+│	│	|	|		└─file.cert		uni-id依赖的配置文件,假如你使用微信发红包功能,需要的证书文件就是放到这里
+│	|	|	└───uni-id				uni-id用户体系 详情
+│	|	├─uni-analyse-searchhot		云端一体搜索模板依赖的云函数 详情
+│	|	└─uni-id-cf				用户中心云函数,实现用户注册、修改密码、发送验证码、快捷登录(微信、短信、账户、一键登录)
+│	└──database						云数据目录
+│		├─db_init.json				db_init.json初始化数据库文件,其中不再包含schema 详情
+│		├─opendb-app-versions.schema.json		应用版本,表结构文件
+│		├─opendb-banner.schema.json	        	横幅数据表,表结构文件
+│		├─opendb-feedback.schema.json	        意见反馈表,表结构文件
+│		├─opendb-news-articles.schema.json	    新闻文章表,表结构文件
+│		├─opendb-news-categories.schema.json	新闻分类表,表结构文件
+│		├─opendb-news-comments.schema.json		新闻评论表,表结构文件
+│		├─opendb-news-favorite.schema.json		新闻收藏表,表结构文件
+│		├─opendb-search-hot.schema.json			热门搜索表,表结构文件
+│		├─opendb-search-log.schema.json			搜索记录表,表结构文件
+│		├─opendb-verify-codes.schema.json		验证码表,表结构文件
+│		├─uni-id-log.schema.json	        	登录日志表,表结构文件
+│		├─uni-id-scores.schema.json	        	用户积分表,表结构文件
+│		└─uni-id-users.schema.json	        	用户表,表结构文件
+├─pages										业务页面文件存放的目录
+│	├─common						
+│	│	└─webview							webview目录
+│	│		└─webview.vue					webview页面	用于实现跨端的web页面浏览
+│	├─grid
+│	│	└─grid.vue	 						带宫格和banner的示例页面
+│	├─list
+│	│	├─list.vue	 						新闻列表
+│	│	├─search
+│	│	│	└─search						云端一体搜索插件
+│	│	└─detail.vue						新闻详情
+│	├─ucenter
+│	│	├─about								关于我们
+│	│	│	└─about
+│	│	├─login-page						登录模块
+│	|	|	├─common						登录模块公共库
+│	│	│	│	├─login-page.css			公共样式库
+│	│	│	│	├─login-page.mixin.js		公共mixin
+│	│	│	│	└─loginSuccess.js			公共登录成功后操作
+│	|	|	├─index							短信验证码登录,手机号码输入页面
+│	|	|	├─phone-code					短信验证码登录,验证码输入页面
+│	|	|	├─pwd-login						账户密码登录
+│	|	|	├─pwd-retrieve					密码重置
+│	│	│	└─register						注册账户模块
+│	│	│		├─validator.js
+│	│	│		└─register.vue
+│	│	├─read-news-log						新闻阅读记录
+│	│	│	└─read-news-log
+│	│	├─settings						
+│	│	│	├─dc-push
+│	│	│	│	└─push.js					push权限操作SDK
+│	│	│	└─settings.vue					app设置
+│	│	├─userinfo							用户个人信息
+│	│	│	├─bind-mobile
+│	│	│	│	└─bind-mobile.vue			绑定手机号码
+│	│	│	├─limeClipper					图片裁剪插件,来源[limeClipper](https://ext.dcloud.net.cn/plugin?id=3594) @作者: 陌上华年
+│	│	│	│	├─images
+│	│	│	│	│	├─photo.svg
+│	│	│	│	│	└─rotate.svg
+│	│	│	│	├─index.css
+│	│	│	│	├─limeClipper.vue
+│	│	│	│	├─README.md
+│	│	│	│	└─utils.js
+│	│	│	├─main.js
+│	│	│	├─cropImage.vue	引用limeClipper的图片裁剪模块,为了方便二开可能会出现兼容`vue`与`nvue`,所以做成了`页面`而不是`组件`
+│	│	│	└─userinfo.vue
+│	|	└─ucenter.vue						用户中心
+│	|
+├─static	 						存放应用引用的本地静态资源(如图片、视频等)的目录,注意:静态资源只能存放于此
+├─uni_modules						存放[uni_module](/uni_modules)规范的插件。
+├─uni_modules_tools					uni_modules插件上传辅助脚本 详情。
+├─main.js							Vue初始化入口文件
+├─App.vue							应用配置,用来配置App全局样式以及监听 应用生命周期
+├─uni-starter.config				uni-starter的前端的配置文件,项目所有模块的配置在这里填写。详见该文件的代码注释。
+├─manifest.json	 					配置应用名称、appid、logo、版本等打包信息,详见
+└─pages.json						配置页面路由、导航条、选项卡等页面类信息,详见
+
+完整的uni-app目录结构[详情](https://uniapp.dcloud.io/frame?id=%e7%9b%ae%e5%bd%95%e7%bb%93%e6%9e%84) + +## 常见API示范 +1. 判断当前用户是否拥有某角色`uniIDHasRole` 演示页面:`/pages/grid/grid` [API文档详情:](https://uniapp.dcloud.io/api/global?id=uniidhasrole) +2. 指纹解锁、人脸解锁 演示页面:`/pages/ucenter/settings/settings` [API文档详情:](https://uniapp.dcloud.io/api/system/authentication) + +## 注意事项 +1. 真机运行需要制作自定义基座,制作后选择运行到自定义基座 +2. 苹果登录的图标,需要满足苹果应用市场的审核规范请勿随便修改;如需修改请先阅读:[Sign in with Apple Button](https://appleid.apple.com/signinwithapple/button) +3. 应用登录功能,默认不勾选同意隐私权限是响应安卓应用市场的规范;请勿修改该逻辑。 + +## FAQ:常见问题 +1. 提示“公共模块uni-id缺少配置信息”解决方案:在cloudfunctions右键‘上传所有云函数、公共模块及actions’之后,需要在cloudfunctions -> common -> uni-config-center 目录单独上传一次,右键‘上传公共模块’。 +2. 本项目代码可以商用,无需为DCloud付费。但不能把本项目的代码改造用于非uni-app和uniCloud的技术体系。即,不能将后台改成php、java等其他后台,这将违反使用许可协议。 + +## 相关案例 +[ + ![](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-f184e7c3-1912-41b2-b81f-435d1b37c7b4/dd4c366f-6165-46c0-8500-5a679d7e5463.jpg) +](https://ext.dcloud.net.cn/search?q=uni-starter) +(点击跳转到案例列表) + + +## 第三方插件(感谢插件作者,排名不分前后): +1. 图片裁剪 [limeClipper](https://ext.dcloud.net.cn/plugin?id=3594) @作者: 陌上华年 +2. 二维码生成 [Sansnn-uQRCode](https://ext.dcloud.net.cn/plugin?id=1287) @作者: 3snn +3. clipboard.js [clipboard](https://clipboardjs.com/) \ No newline at end of file diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..e87aa74 --- /dev/null +++ b/changelog.md @@ -0,0 +1,174 @@ +## 1.1.22(2021-11-10) +删除`common/openApp.js`中可选链操作符,解决vue3版本在hbuilderX内置浏览器不兼容的问题 +## 1.1.21(2021-11-10) +新增app端列表页面使用原生list下拉刷新 +## 1.1.20(2021-11-08) +修复vue3版某些情况下i18n报错的问题 +## 1.1.19(2021-11-08) +配置文件`uni-starter.config.js`默认关闭i18n多语言国际化 +## 1.1.18(2021-10-14) +使用2.0版`uni-share`。当显示分享窗口时,监听返回操作(如:物理返回,全面屏手机侧滑)关闭分享窗口,而不是关闭当前页面。 +## 1.1.17(2021-10-12) +- 更新文档 +- 修复list页面where条件中缺少&符,导致的错误 +## 1.1.16(2021-10-05) +在控制台提示:开启多语言国际化,将获取i18n中配置的tabbar的值覆盖pages.json中配置的tabbar的值 +## 1.1.15(2021-10-02) +新增,支持配置是否开启i18n多语言国际化。 +配置文件:`uni-starter.config.js` +` +"i18n":{ + "enable":true //默认启用,国际化。如果你不想使用国际化相关功能,请改为false +} +` +## 1.1.14(2021-09-30) +1. 通过微信小程序登录自动保存`sessionKey`到`uni-id-users`表 +2. 我的-设置-个人资料 点击绑定手机号码,完善账号信息支持以下三种策略: + - APP端,(如果支持)使用"通过运营商一键获取手机号码" + - 微信小程序端,支持"一键获取微信绑定的手机号" + - 其他端,通过手机验证码 +## 1.1.13(2021-09-29) +修复search页面因多语言国际化导致的白屏问题 +## 1.1.12(2021-09-28) +1. 改造微信登录逻辑,直接使用`uni.login`参数`"onlyAuthorize":true`实现 +2. 修复,一键登录弹出层,已勾选“同意隐私政策协议”点击自定义登录按钮,报“你未同意隐私政策协议”的bug +## 1.1.11(2021-09-24) +优化邀请下载app页(`pages/ucenter/invite`)下载按钮闪烁的问题 +## 1.1.10(2021-09-23) +修复获取验证码按钮的文字,在中文模式下显示为英文的问题 +## 1.1.9(2021-09-16) +修复由多语言切换功能引起的隐私政策协议标题链接被重写的问题 +## 1.1.8(2021-09-15) +更新数据表guestbook的schema中更新权限的配置 +## 1.1.7(2021-09-14) +更新数据表opendb-news-articles的schema中的权限配置 +## 1.1.6(2021-09-13) +纠正错误schema权限表达式`doc.uid`为`doc.user_id` +## 1.1.5(2021-09-01) +为了更直观理解路由拦截。移除路由拦截器中,默认过滤登录相关页面拦截的逻辑。确保所有白名单页面均在配置文件router.visitor中体现 +## 1.1.4(2021-08-31) +修改错误的文章表`SChema`的读权限表达式 +## 1.1.3(2021-08-31) +修复在微信小程序端默认语言为英文的问题 +## 1.1.2(2021-08-30) +修复在微信小程序下切换语言报`locale`不存在的问题 +## 1.1.1(2021-08-30) +- 解决3.2.6以下版本hbuilderx,编译的项目报`uni.setLocale`不存在的问题 +## 1.1.0(2021-08-27) +- APP端支持vue3 (hbuilderx 3.2.5+) +- 支持国际化 中英文切换 +- 新增留言板示例 +- 修复签到的时区问题 +## 1.0.48(2021-08-10) +- 修复登录成功后响应体包含`userInfo.password`的问题 +- 修改了`uni-id-users`表的schema中字段username的编辑权限,防止用户通过clientDB绕过用户名不能重复的规则更新用户名的问题 +## 1.0.47(2021-08-09) +- 更新文档快速体验部署流程 +- 修复一键登录优先时报变量找不到的问题 +## 1.0.46(2021-08-05) +清理多余文件 +## 1.0.45(2021-08-05) +默认首页为nvue页面+fast +## 1.0.44(2021-08-05) +解决首页为非nvue页面时白屏的问题。 +- 注意:本次在`common/appInit.js`中修改了路由拦截的逻辑,是个兼容方案;当首页为非nvue页面,路由拦截器逻辑会在加载首页时执行。接下来新版本的hx编译的uni-app项目无论首页是否为nvue都不走拦截器,保持各端逻辑一致。 +## 1.0.43(2021-08-02) +1. 微信小程序端,新增:微信登录成功后,弹出是否"获取微信头像和昵称,完善个人资料"的弹框 +2. APP端,新增逻辑:微信登录成功后,自动获取用户的微信昵称和头像完善用户个人资料 +- 提示:因为微信的头像一旦更换,微信返回的头像url会失效。所以,以上两示例功能将url(客户端:下载到临时目录/服务端:转为Buffer)再上传到uniCloud云存储中再使用。 +## 1.0.42(2021-07-29) +新增绑定手机号码页面前端校验 +## 1.0.41(2021-07-27) +1. 支持vue3.0 +2. 去掉App.vue全局样式,避免与非flex布局的页面样式冲突 +## 1.0.40(2021-07-22) +1. 调整使用正则表达式配置强制登录功能的写法,解决在小程序端的兼容问题。 +2. 新增签到功能(培养用户习惯,提升用户粘性)。支持:每日签到奖励、周期性连续7日签到,奖励翻倍。 +## 1.0.39(2021-07-19) +1. 强制登录配置,新增白名单模式 +2. 强制登录配置,支持正则表达式 +## 1.0.38(2021-07-17) +删除多余文件 +## 1.0.37(2021-07-14) +去掉配置文件:`uni-starter.config.js`,`h5` —> `url`结尾的`/` +## 1.0.36(2021-07-14) +剪切板中的邀请码,添加标识性前缀 `uniInvitationCode:` +## 1.0.35(2021-07-12) +1. H5端默认不开启,隐私权限协议签署页面。因为网页端没有什么隐私权限能被获取,目前全球仅欧盟有要求;如有需要请手动开启 +2. 在列表页演示,如何在onShow生命周期获取设备位置,并在设备或者应用没有权限时自动引导。设置完毕自动重新获取。[更多点此查看插件介绍](https://ext.dcloud.net.cn/plugin?name=json-gps) +## 1.0.34(2021-07-08) +修复,打开登录页时携带参数,导致的快捷登录方式重复的问题 +## 1.0.33(2021-07-06) +修复,点击短信验证码登录打开的页面不正确的问题 +## 1.0.32(2021-07-06) +修复,仅配置一种快捷登录时的错误 +## 1.0.31(2021-07-02) +优化项目文档 +## 1.0.30(2021-07-01) +1. 简化宫格页面写法,方便理解如何控制不同状态角色的用户是否可见哪些元素。 +2. uni-id-cf发送短信验证码api,默认注释掉:虚拟发送短信验证码的代码块。 +3. uni-id-cf统一action名称为驼峰法 +## 1.0.29(2021-06-29) +1. 修复在安卓10以下设备,操作登录获取不到oaid会直接导致登录失败的bug +2. 修复uniCloud版本为阿里云版时删除头像设置失败,腾讯云版删除头像后二次上传失败的问题 +## 1.0.28(2021-06-28) +修复云函数uni-id-cf的resetPwdBySmsCode接口,未注册过的用户也能调用的问题 +## 1.0.27(2021-06-25) +修改文档,新增h5版演示示例 +## 1.0.26(2021-06-24) +升级用户头像上传的裁切功能,app端为原生裁剪其他端保持原来方式。数据表字段改用avatar_file存储file对象方便做图片的回显 +## 1.0.25(2021-06-23) +预置uniCloud admin依赖的uniCloud文件,方便uniCloud admin与uni-starter配套使用时免做文件迁移 +## 1.0.24(2021-06-23) +删除callFunction拦截器中多余的代码 +## 1.0.23(2021-06-22) +更正调试遗留的uni-config-center/uni-id/config.json的tokenExpiresIn=1配置问题,改为默认值7200 +## 1.0.22(2021-06-22) +1. 新增一键登录授权界面的其他快捷登录按钮 +2. 优化uni-quick-login组件代码 +3. 调整隐私政策协议框勾选逻辑:在登录页面已勾选,同步勾选。如果没勾选需要手动勾选(为符合应用市场上架要求) +4. 调整登录页隐私政策协议框位置。 +5. 增强路由拦截,新增判断token是否过期。 +## 1.0.21(2021-06-21) +优化:uni_modules模式使用uni-id-cf,方便uni-starter与uniCloud-admin的uni-id-cf同步更新。 +## 1.0.20(2021-06-18) +1.H5端新增,强制要求用户同意隐私协议 2.兼容ios端自动设置打开下载页用户的剪切板为邀请者的inviteCode 3.成功注册用户,且请求体含邀请码inviteCode自动关联裂变关系 +## 1.0.19(2021-06-17) +1.新增获取邀请码接口getUserInviteCode 2.在邀请用户下载应用页面,自动设置被邀请用户的剪切板为邀请者的code(仅支持安卓端) 3.在注册或登录并注册请求时自动添加剪切板中的请求参数 4.统一接口名称为驼峰法 +## 1.0.18(2021-06-15) +修复,APP端有安装微信客户端但未显示微信登录快捷键的问题 +## 1.0.17(2021-06-09) +修复,非APP端deviceInfo为空引起的登录失败问题 +## 1.0.16(2021-06-08) +新增,操作注册/登录操作自动获取客户端设备:push_clientid、imei、oaid、idfa新增/更新到数据表uni-id-device新增,操作注册/登录操作自动获取客户端设备:push_clientid、imei、oaid、idfa新增/更新到数据表uni-id-device +## 1.0.15(2021-06-07) +为迎合苹果App Store的规则,登录与分享功能项显示之前自动检测是否安装了对应客户端。比如:设备未安装微信则不显示微信快捷登录和微信分享选项。为迎合苹果App Store的规则,登录与分享功能项显示之前自动检测是否安装了对应客户端。比如:设备未安装微信则不显示微信快捷登录和微信分享选项。 +## 1.0.14(2021-06-07) +修改错误的表名称uni-verify为opendb-verify-codes +## 1.0.13(2021-06-04) +新增一键登录界面的第三方快捷登录按钮 +## 1.0.12(2021-05-28) +修复拦截器在ios app端会报错:Unhandled promise...的问题 +## 1.0.10(2021-05-27) +新增callfunction的拦截器废除this.request的写法。为callFunction添加:请求失败是否断网判断并提示、恢复网络自动重新执行、自动处理响应体:token过期自动跳转到登录页面、token自动续期 +## 1.0.9(2021-05-23) +修复变量被重复定义的问题 +## 1.0.8(2021-05-22) +宫格页(/pages/grid/grid),新增根据当前用户是否登录、是否为管理员的角色来决定是否显示的示范 +## 1.0.7(2021-05-22) +删除多余数据 +## 1.0.6(2021-05-22) +修复当username(用户名&密码)为第一优先级的登录方式时。无法切换到smsCode(短信验证码)登录方式 +## 1.0.5(2021-05-20) +改用uni_modules方式处理图片选择api时无权限,引导用户快捷打开系统设置 +## 1.0.4(2021-05-19) +为方便部署,添加空的manifest.json uni-config-center下的uni-id配置 +## 1.0.3(2021-05-18) +重大调整,原云函数名称:user-center改名叫uni-id-cf +修复,绑定手机号码场景。因手机未插SIM导致的一键登录失败后未直接跳到获取短信验证码方式绑定 +## 1.0.2(2021-05-17) +添加 uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/uni-id/config.json 文件 +## 1.0.1(2021-05-17) +manifest.json 在小程序平台增加了一个配置项 betterScopedSlots,启用新的作用域插槽编译,用于支持作用域插槽内使用复杂表达式。 +## 1.0.0(2021-05-17) +第一版 \ No newline at end of file diff --git a/common/appInit.js b/common/appInit.js new file mode 100644 index 0000000..a3d285a --- /dev/null +++ b/common/appInit.js @@ -0,0 +1,457 @@ +import uniStarterConfig from '@/uni-starter.config.js'; +import store from '@/store' +//应用初始化页 +// #ifdef APP-PLUS +import checkUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update'; +import callCheckVersion from '@/uni_modules/uni-upgrade-center-app/utils/call-check-version'; +import interceptorChooseImage from '@/uni_modules/json-interceptor-chooseImage/js_sdk/main.js'; +// #endif +const db = uniCloud.database() +export default async function() { + let loginConfig = uniStarterConfig.router.login + //清除有配置但设备环境不支持的登录项 + // #ifdef APP-PLUS + await new Promise((callBack) => { + plus.oauth.getServices(oauthServices => { + loginConfig = loginConfig.filter(item => { + if (["univerify", "weixin", "apple"].includes(item)) { + let index = oauthServices.findIndex(e => e.id == item) + if (index == -1) { + return false + } else { + return oauthServices[index].nativeClient + } + } else { + return true + } + }) + if (loginConfig.includes('univerify')) { //一键登录 功能预登录 + uni.preLogin({ + provider: 'univerify', + complete: e => { + console.log(e); + } + }) + } + callBack() + }, err => { + console.error('获取服务供应商失败:' + JSON.stringify(err)); + }) + }) + // #endif + + //非app移除:一键登录、苹果登录;h5移除微信登录,如果你做微信公众号登录需要将此行移除 + // #ifndef APP-PLUS + loginConfig = loginConfig.filter(item => { + return ![ + 'univerify', + 'apple', + // #ifdef H5 + 'weixin' + // #endif + ].includes(item) + }) + // #endif + + uniStarterConfig.router.login = loginConfig + + // uniStarterConfig挂载到getApp().globalData.config + setTimeout(() => { + getApp({ + allowDefault: true + }).globalData.config = uniStarterConfig; + }, 1) + + + // 初始化appVersion(仅app生效) + initAppVersion(); + + // #ifdef APP-PLUS + // 实现,路由拦截。当应用无访问摄像头/相册权限,引导跳到设置界面 + interceptorChooseImage() + // #endif + + + //clientDB的错误提示 + function onDBError({ + code, // 错误码详见https://uniapp.dcloud.net.cn/uniCloud/clientdb?id=returnvalue + message + }) { + console.log('onDBError', { + code, + message + }); + // 处理错误 + console.error(code, message); + if ([ + 'TOKEN_INVALID_INVALID_CLIENTID', + 'TOKEN_INVALID', + 'TOKEN_INVALID_TOKEN_EXPIRED', + 'TOKEN_INVALID_WRONG_TOKEN', + 'TOKEN_INVALID_ANONYMOUS_USER', + + ].includes(code)) { + uni.navigateTo({ + url: '/pages/ucenter/login-page/index/index' + }) + } + } + // 绑定clientDB错误事件 + db.on('error', onDBError) + + // 解绑clientDB错误事件 + //db.off('error', onDBError) + db.on('refreshToken', function({ + token, + tokenExpired + }) { + console.log('监听到clientDB的refreshToken', { + token, + tokenExpired + }); + store.commit('user/login', { + token, + tokenExpired + }) + }) + + const Debug = false; + //拦截器封装callFunction + let callFunctionOption; + uniCloud.addInterceptor('callFunction', { + async invoke(option) { + // #ifdef APP-PLUS + // 判断如果是执行登录(无论是哪种登录方式),就记录用户的相关设备id + if (option.name == 'uni-id-cf' && + (option.data.action == 'register' || option.data.action.slice(0, 5) == 'login') + ) { + let oaid = await new Promise((callBack, fail) => { + if (uni.getSystemInfoSync().platform == "android") { + plus.device.getOAID({ + success: function(e) { + callBack(e.oaid) + // console.log('getOAID success: '+JSON.stringify(e)); + }, + fail: function(e) { + callBack() + console.log('getOAID failed: ' + JSON.stringify(e)); + } + }); + } else { + callBack() + } + }) + + let imei = await new Promise((callBack, fail) => { + if (uni.getSystemInfoSync().platform == "android") { + plus.device.getInfo({ + success: function(e) { + callBack(e.imei) + // console.log('getOAID success: '+JSON.stringify(e)); + }, + fail: function(e) { + callBack() + console.log('getOAID failed: ' + JSON.stringify(e)); + } + }); + } else { + callBack() + } + }) + + let push_clientid = '', + idfa = plus.storage.getItem('idfa') || ''; //idfa有需要的用户在应用首次启动时自己获取存储到storage中 + + try { + push_clientid = plus.push.getClientInfo().clientid + } catch (e) { + uni.showModal({ + content: '获取推送标识失败。如果你的应用不需要推送功能,请注释掉本代码块', + showCancel: false, + confirmText: "好的" + }); + console.log(e) + } + + let deviceInfo = { + push_clientid, // 获取匿名设备标识符 + imei, + oaid, + idfa + } + console.log("重新登录/注册,获取设备id", deviceInfo); + option.data.deviceInfo = deviceInfo + + // #ifndef H5 + //注册可能不仅仅走register接口,还有登录并注册的接口 + option.data.inviteCode = await new Promise((callBack) => { + uni.getClipboardData({ + success: function(res) { + if (res.data.slice(0, 18) == 'uniInvitationCode:') { + let uniInvitationCode = res.data.slice(18, 38) + console.log('当前用户是其他用户推荐下载的,推荐者的code是:' + + uniInvitationCode); + // uni.showModal({ + // content: '当前用户是其他用户推荐下载的,推荐者的code是:'+uniInvitationCode, + // showCancel: false + // }); + callBack(uniInvitationCode) + //当前用户是其他用户推荐下载的。这里登记他的推荐者id 为当前用户的myInviteCode。判断如果是注册 + } else { + callBack(false) + } + }, + fail() { + callBack(false) + } + }); + }) + // #endif + } + // #endif + // console.log(JSON.stringify(option)); + callFunctionOption = option + }, + complete(e) { + // console.log(JSON.stringify(e)); + }, + fail(e) { // 失败回调拦截 + if (Debug) { + uni.showModal({ + content: JSON.stringify(e), + showCancel: false + }); + console.error(e); + } else { + uni.showModal({ + content: "系统错误请稍后再试!", + showCancel: false, + confirmText: "知道了" + }); + } + //如果执行错误,检查是否断网 + uni.getNetworkType({ + complete: res => { + // console.log(res); + if (res.networkType == 'none') { + uni.showToast({ + title: '手机网络异常', + icon: 'none' + }); + console.log('手机网络异常'); + let callBack = res => { + console.log(res); + if (res.isConnected) { + uni.showToast({ + title: '恢复联网自动重新执行', + icon: 'none' + }); + console.log(res.networkType, "恢复联网自动重新执行"); + uni.offNetworkStatusChange(e => { + console.log("移除监听成功", e); + }) + //恢复联网自动重新执行 + uniCloud.callFunction(callFunctionOption) + uni.offNetworkStatusChange(callBack); + } + } + uni.onNetworkStatusChange(callBack); + } + } + }); + }, + success: (e) => { + // console.log(e); + const { + token, + tokenExpired + } = e.result + if (token && tokenExpired) { + store.commit('user/login', { + token, + tokenExpired + }) + } + switch (e.result.code) { + case 403: + uni.navigateTo({ + url: "/pages/ucenter/login-page/index/index" + }) + break; + case 30203: + uni.navigateTo({ + url: "/pages/ucenter/login-page/index/index" + }) + break; + case 50101: + uni.showToast({ + title: e.result.msg, + icon: 'none', + duration: 2000 + }); + break; + default: + console.log('code的值是:' + e.result.code, '可以在上面添加case,自动处理响应体'); + break; + } + + switch (e.result.errCode) { + case 'uni-id-token-not-exist': + uni.showToast({ + title: '登录信息失效', + icon: 'none' + }); + uni.navigateTo({ + url: "/pages/ucenter/login-page/index/index" + }) + break; + default: + break; + } + } + }) + + //自定义路由拦截 + const { + "router": { + needLogin, + visitor, + login + } + } = uniStarterConfig //需要登录的页面 + + let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"]; + list.forEach(item => { //用遍历的方式分别为,uni.navigateTo,uni.redirectTo,uni.reLaunch,uni.switchTab这4个路由方法添加拦截器 + uni.addInterceptor(item, { + invoke(e) { // 调用前拦截 + //获取用户的token + const token = uni.getStorageSync('uni_id_token'), + //token是否已失效 + tokenExpired = uni.getStorageSync('uni_id_token_expired') < Date.now(), + //获取要跳转的页面路径(url去掉"?"和"?"后的参数) + url = e.url.split('?')[0]; + //获取要前往的页面路径(即url去掉"?"和"?"后的参数) + const pages = getCurrentPages(); + if (!pages.length) { + console.log("首页启动调用了"); + return e + } + const fromUrl = pages[pages.length - 1].route; + + let inLoginPage = fromUrl.split('/')[2] == 'login-page' + + //控制登录优先级 + if ( //判断当前窗口是否为登录页面,如果是则不重定向路由 + url == '/pages/ucenter/login-page/index/index' && + !inLoginPage + ) { + //一键登录(univerify)、账号(username)、验证码登录(短信smsCode) + if (login[0] == 'username') { + e.url = "/pages/ucenter/login-page/pwd-login/pwd-login" + } else { + if (e.url == url) { + e.url += '?' + } //添加参数之前判断是否带了`?`号如果没有就补上,因为当开发场景本身有参数的情况下是已经带了`?`号 + e.url += "type=" + login[0] + } + } else { + //拦截强制登录页面 + let pass = true + //pattern + if (needLogin) { + pass = needLogin.every((item) => { + if (typeof(item) == 'object' && item.pattern) { + return !item.pattern.test(url) + } + return url != item + }) + // console.log({pass}) + } + if (visitor) { + pass = visitor.some((item) => { + if (typeof(item) == 'object' && item.pattern) { + return item.pattern.test(url) + } + return url == item + }) + // console.log({pass}) + } + + if (!pass && (token == '' || tokenExpired)) { + uni.showToast({ + title: '请先登录', + icon: 'none' + }) + uni.navigateTo({ + url: "/pages/ucenter/login-page/index/index" + }) + return false + } + } + return e + }, + fail(err) { // 失败回调拦截 + console.log(err); + if (Debug) { + console.log(err); + uni.showModal({ + content: JSON.stringify(err), + showCancel: false + }); + } + } + }) + }) + + // #ifdef APP-PLUS + // 监听并提示设备网络状态变化 + uni.onNetworkStatusChange(res => { + console.log(res.isConnected); + console.log(res.networkType); + if (res.networkType != 'none') { + uni.showToast({ + title: '当前网络类型:' + res.networkType, + icon: 'none', + duration: 3000 + }) + } else { + uni.showToast({ + title: '网络类型:' + res.networkType, + icon: 'none', + duration: 3000 + }) + } + }); + // #endif + +} +/** + * // 初始化appVersion + */ +function initAppVersion() { + // #ifdef APP-PLUS + let appid = plus.runtime.appid; + plus.runtime.getProperty(appid, (wgtInfo) => { + let appVersion = plus.runtime; + let currentVersion = appVersion.versionCode > wgtInfo.versionCode ? appVersion : wgtInfo; + getApp({ + allowDefault: true + }).appVersion = { + ...currentVersion, + appid, + hasNew: false + } + // 检查更新小红点 + callCheckVersion().then(res => { + // console.log('检查是否有可以更新的版本', res); + if (res.result.code > 0) { + // 有新版本 + getApp({ + allowDefault: true + }).appVersion.hasNew = true; + } + }) + }); + // 检查更新 + // #endif +} diff --git a/common/openApp.js b/common/openApp.js new file mode 100644 index 0000000..6fbbbb4 --- /dev/null +++ b/common/openApp.js @@ -0,0 +1,36 @@ +/* + 创建在h5端全局悬浮引导用户下载app的功能, + 如不需要本功能直接移除配置文件uni-starter.config.js下的h5/openApp即可 +*/ + +import CONFIG from '../uni-starter.config.js'; + +const CONFIG_OPEN = CONFIG.h5.openApp || {}; +// 仅H5端添加"打开APP" +export default function() { + // #ifdef H5 + if (!CONFIG_OPEN.openUrl) return; + + let openLogo = CONFIG_OPEN.logo ? + `` : ''; + let openApp = document.createElement("div"); + openApp.id = 'openApp'; + openApp.style = + 'position: fixed;background:#FFFFFF;box-shadow: #eeeeee 1px 1px 9px; ;top: 0;left: 0;right: 0;z-index: 999;width: 100%;height: 45px;display: flex;flex-direction: row;justify-content: space-between;align-items: center;box-sizing: border-box;padding: 0 0.5rem;' + openApp.innerHTML = ` +
+ ${openLogo} +
${CONFIG_OPEN.appname || ''}
+
+
下载app
+ `; + document.body.insertBefore(openApp, document.body.firstChild); + document.body.style = 'height:calc(100% - 45px); margin-top:45px;'; + openApp.addEventListener('click', e => { + var target = e.target || e.srcElement; + if (target.className.indexOf('openBtn') >= 0) { + window.location.href = CONFIG_OPEN.openUrl; + } + }) + //#endif +} diff --git a/common/uni-ui.scss b/common/uni-ui.scss new file mode 100644 index 0000000..de868c7 --- /dev/null +++ b/common/uni-ui.scss @@ -0,0 +1,118 @@ + +.uni-flex { + display: flex; +} + +.uni-flex-row { + @extend .uni-flex; + flex-direction: row; + box-sizing: border-box; +} + +.uni-flex-column { + @extend .uni-flex; + flex-direction: column; +} + +.uni-color-gary { + color: #3b4144; +} + +/* 标题 */ +.uni-title { + display: flex; + margin-bottom: $uni-spacing-col-base; + font-size: $uni-font-size-lg; + font-weight: bold; + color: #3b4144; +} + +.uni-title-sub { + display: flex; + font-size: $uni-font-size-base; + font-weight: 500; + color: #3b4144; +} + +/* 描述 额外文本 */ +.uni-note { + margin-top: 10px; + color: #999; + font-size: $uni-font-size-sm; +} + +/* 列表内容 */ +.uni-list-box { + @extend .uni-flex-row; + flex: 1; + margin-top: 10px; +} + +/* 略缩图 */ +.uni-thumb { + flex-shrink: 0; + margin-right: $uni-spacing-row-base; + width: 125px; + height: 75px; + border-radius: $uni-border-radius-lg; + overflow: hidden; + border: 1px #f5f5f5 solid; + image { + width: 100%; + height: 100%; + } +} + +.uni-media-box { + @extend .uni-flex-row; + border-radius: $uni-border-radius-lg; + overflow: hidden; + .uni-thumb { + margin: 0; + margin-left: 4px; + flex-shrink: 1; + width: 33%; + border-radius:0; + &:first-child { + margin: 0; + } + } +} + +/* 内容 */ +.uni-content { + @extend .uni-flex-column; + justify-content: space-between; +} + +/* 列表footer */ +.uni-footer { + @extend .uni-flex-row; + justify-content: space-between; + margin-top: $uni-spacing-col-lg; +} +.uni-footer-text { + font-size: $uni-font-size-sm; + color: $uni-text-color-grey; + margin-left: 5px; +} + +/* 标签 */ + +.uni-tag { + flex-shrink: 0; + padding: 0 5px; + border: 1px $uni-border-color solid; + margin-right: $uni-spacing-row-sm; + border-radius: $uni-border-radius-base; + background: $uni-bg-color-grey; + color: $uni-text-color; + font-size: $uni-font-size-sm; +} + +/* 链接 */ +.uni-link { + margin-left: 10px; + color: $uni-text-color; + text-decoration: underline; +} diff --git a/components/Sansnn-uQRCode/README.md b/components/Sansnn-uQRCode/README.md new file mode 100644 index 0000000..c99382f --- /dev/null +++ b/components/Sansnn-uQRCode/README.md @@ -0,0 +1,172 @@ +> 插件来源:[https://ext.dcloud.net.cn/plugin?id=1287](https://ext.dcloud.net.cn/plugin?id=1287) +##### 以下是作者写的插件介绍: + +# uQRCode + +### 云函数版二维码生成插件explain-qrcode现已发布,URL化后一句代码即可生成,有网就有二维码,100%生成成功,不会因为平台差异,设备差异导致生成失败,无需在前端做适配和兼容,极力推荐。插件地址:[explain-qrcode云函数二维码生成](https://ext.dcloud.net.cn/plugin?id=3359) + +uQRCode 生成方式简单,可扩展性高,如有复杂需求可通过自定义组件或修改源码完成需求。已测试H5、微信小程序、iPhoneXsMax真机。 + +本示例项目中的自定义组件旨在抛砖引玉,有其他需求的朋友可自行扩展,自定义组件参考 ``/components/uni-qrcode/uni-qrcode.vue`` ,自定义组件使用案例参考 ``/pages/component/qrcode/qrcode.vue`` 。 + +联系方式:QQ540000228。 + +最近一次用于更新代码的 HBuilder X 版本为 2.8.11。 + +### 二维码 +**什么是QR码** + +QR码属于矩阵式二维码中的一个种类,由DENSO(日本电装)公司开发,由JIS和ISO将其标准化。 + +**QR码的特点** + +一是高速读取(QR就是取自“Quick Response”的首字母),通过摄像头从拍摄到解码到显示内容也就三秒左右,对摄像的角度也没有什么要求; + +二是高容量、高密度,理论上内容经过压缩处理后可以存7089个数字,4296个字母和数字混合字符,2953个8位字节数据,1817个汉字; + +三是支持纠错处理,按照QR码的标准文档说明,QR码的纠错分为4个级别,分别是: +- level L : 最大 7% 的错误能够被纠正; +- level M : 最大 15% 的错误能够被纠正; +- level Q : 最大 25% 的错误能够被纠正; +- level H : 最大 30% 的错误能够被纠正; + +四是结构化,看似无规则的图形,其实对区域有严格的定义。 + +更多二维码介绍及原理:[https://blog.csdn.net/jason_ldh/article/details/11801355](https://blog.csdn.net/jason_ldh/article/details/11801355) + +### 使用方式 + +在 ``script`` 中引用组件 + +```javascript +import uQRCode from '@/common/uqrcode.js' +``` + +在 ``template`` 中创建 ```` + +```html + +``` + +在 ``script`` 中调用 ``make()`` 方法 + +```javascript +export default { + methods: { + async make() { + // 回调方式 + uQRCode.make({ + canvasId: 'qrcode', + componentInstance: this, + text: 'uQRCode', + size: 354, + margin: 10, + backgroundColor: '#ffffff', + foregroundColor: '#000000', + fileType: 'jpg', + errorCorrectLevel: uQRCode.errorCorrectLevel.H, + success: res => { + console.log(res) + } + }) + + // Promise + uQRCode.make({ + canvasId: 'qrcode', + componentInstance: this, + text: 'uQRCode', + size: 354, + margin: 10, + backgroundColor: '#ffffff', + foregroundColor: '#000000', + fileType: 'jpg', + errorCorrectLevel: uQRCode.errorCorrectLevel.H + }).then(res => { + console.log(res) + }) + + // 同步等待 + var res = await uQRCode.make({ + canvasId: 'qrcode', + componentInstance: this, + text: 'uQRCode', + size: 354, + margin: 10, + backgroundColor: '#ffffff', + foregroundColor: '#000000', + fileType: 'jpg', + errorCorrectLevel: uQRCode.errorCorrectLevel.H + }) + console.log(res) + } + } +} +``` + +### 属性说明 + +|属性名|说明| +|---|:---| +|errorCorrectLevel|纠错等级,包含 `errorCorrectLevel.L`、`errorCorrectLevel.M`、`errorCorrectLevel.Q`、`errorCorrectLevel.H` 四个级别,`L`: 最大 7% 的错误能够被纠正;`M`: 最大 15% 的错误能够被纠正;`Q`: 最大 25% 的错误能够被纠正;`H`: 最大 30% 的错误能够被纠正。| +|defaults|二维码生成参数的默认值。| + +### 方法说明 + +|方法名|说明| +|---|:---| +|[make](#makeoptions)|生成二维码。| + +### make(options) + +生成二维码 + +**options参数说明:** + +|参数|类型|必填|说明| +|---|---|---|:---| +|canvasId|String|是|画布标识,传入 `` 的 `canvas-id`| +|componentInstance|Object|否|自定义组件实例 `this` ,表示在这个自定义组件下查找拥有 `canvas-id` 的 `` ,如果省略,则不在任何自定义组件内查找| +|text|String|是|二维码内容| +|size|Number|否|画布尺寸大小,请与 `` 所设 `width` , `height` 保持一致(默认:`354`)| +|margin|Number|否|边距,二维码实际尺寸会根据所设边距值进行缩放调整(默认:`0`)| +|backgroundColor|String|否|背景色,若设置为透明背景, `fileType` 需设置为 `'png'` , 然后设置背景色为 `'rgba(255,255,255,0)'` 即可(默认:`'#ffffff'`)| +|foregroundColor|String|否|前景色(默认:`'#000000'`)| +|fileType|String|否|输出图片的类型,只支持 `'jpg'` 和 `'png'`(默认:`'png'`)| +|errorCorrectLevel|Number|否|纠错等级,参考属性说明 `errorCorrectLevel`(默认:`errorCorrectLevel.H`)| + +### 使用建议 +canvas在二维码生成中请当做一个生成工具来看待,它的作用仅是绘制出二维码。应把生成回调得到的资源保存并使用,显示用image图片组件,原因是方便操作,例如调整大小,或是H5端长按保存或识别,所以canvas应将它放在看不见的地方。不能用`display:none;overflow:hidden;`隐藏,否则生成空白。这里推荐canvas的隐藏样式代码 +```html + +``` + +### 常见问题 +**二维码生成不完整** + +canvas宽高必须和size一致,并且size的单位是px,如果canvas的单位是rpx,那么不同设备屏幕分辨率不一样,rpx转换成px后的画布尺寸不足以放下全部内容,实际绘制图案超出,就会出现不完整的情况。 + +**如何扫码跳转指定网页** + +text参数直接放入完整的网页地址即可,例如:`https://ext.dcloud.net.cn/plugin?id=1287`。微信客户端不能是ip地址。 + +**小程序、APP报错** + +canvas不支持放在 `slot` 插槽,请尽量放在模板根节点,也就是第一个 `` 标签里面 + +**H5长按识别** + +canvas无法长按识别,长按识别需要是图片才行,所以只需将回调过来的资源用image组件显示即可。 + +### Tips +- 示例项目中的图片采集于互联网,仅作为案例展示,不作为广告/商业,如有侵权,请告知删除。下载使用的用户,请勿把示例项目中的图片应用到你的项目。 \ No newline at end of file diff --git a/components/Sansnn-uQRCode/Sansnn-uQRCode.vue b/components/Sansnn-uQRCode/Sansnn-uQRCode.vue new file mode 100644 index 0000000..0433fae --- /dev/null +++ b/components/Sansnn-uQRCode/Sansnn-uQRCode.vue @@ -0,0 +1,198 @@ + + + diff --git a/components/Sansnn-uQRCode/uqrcode.js b/components/Sansnn-uQRCode/uqrcode.js new file mode 100644 index 0000000..311ce1d --- /dev/null +++ b/components/Sansnn-uQRCode/uqrcode.js @@ -0,0 +1,1438 @@ +//--------------------------------------------------------------------- +// github https://github.com/Sansnn/uQRCode +//--------------------------------------------------------------------- + +let uQRCode = {}; + +(function() { + //--------------------------------------------------------------------- + // QRCode for JavaScript + // + // Copyright (c) 2009 Kazuhiko Arase + // + // URL: http://www.d-project.com/ + // + // Licensed under the MIT license: + // http://www.opensource.org/licenses/mit-license.php + // + // The word "QR Code" is registered trademark of + // DENSO WAVE INCORPORATED + // http://www.denso-wave.com/qrcode/faqpatent-e.html + // + //--------------------------------------------------------------------- + + //--------------------------------------------------------------------- + // QR8bitByte + //--------------------------------------------------------------------- + + function QR8bitByte(data) { + this.mode = QRMode.MODE_8BIT_BYTE; + this.data = data; + } + + QR8bitByte.prototype = { + + getLength: function(buffer) { + return this.data.length; + }, + + write: function(buffer) { + for (var i = 0; i < this.data.length; i++) { + // not JIS ... + buffer.put(this.data.charCodeAt(i), 8); + } + } + }; + + //--------------------------------------------------------------------- + // QRCode + //--------------------------------------------------------------------- + + function QRCode(typeNumber, errorCorrectLevel) { + this.typeNumber = typeNumber; + this.errorCorrectLevel = errorCorrectLevel; + this.modules = null; + this.moduleCount = 0; + this.dataCache = null; + this.dataList = new Array(); + } + + QRCode.prototype = { + + addData: function(data) { + var newData = new QR8bitByte(data); + this.dataList.push(newData); + this.dataCache = null; + }, + + isDark: function(row, col) { + if (row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) { + throw new Error(row + "," + col); + } + return this.modules[row][col]; + }, + + getModuleCount: function() { + return this.moduleCount; + }, + + make: function() { + // Calculate automatically typeNumber if provided is < 1 + if (this.typeNumber < 1) { + var typeNumber = 1; + for (typeNumber = 1; typeNumber < 40; typeNumber++) { + var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, this.errorCorrectLevel); + + var buffer = new QRBitBuffer(); + var totalDataCount = 0; + for (var i = 0; i < rsBlocks.length; i++) { + totalDataCount += rsBlocks[i].dataCount; + } + + for (var i = 0; i < this.dataList.length; i++) { + var data = this.dataList[i]; + buffer.put(data.mode, 4); + buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber)); + data.write(buffer); + } + if (buffer.getLengthInBits() <= totalDataCount * 8) + break; + } + this.typeNumber = typeNumber; + } + this.makeImpl(false, this.getBestMaskPattern()); + }, + + makeImpl: function(test, maskPattern) { + + this.moduleCount = this.typeNumber * 4 + 17; + this.modules = new Array(this.moduleCount); + + for (var row = 0; row < this.moduleCount; row++) { + + this.modules[row] = new Array(this.moduleCount); + + for (var col = 0; col < this.moduleCount; col++) { + this.modules[row][col] = null; //(col + row) % 3; + } + } + + this.setupPositionProbePattern(0, 0); + this.setupPositionProbePattern(this.moduleCount - 7, 0); + this.setupPositionProbePattern(0, this.moduleCount - 7); + this.setupPositionAdjustPattern(); + this.setupTimingPattern(); + this.setupTypeInfo(test, maskPattern); + + if (this.typeNumber >= 7) { + this.setupTypeNumber(test); + } + + if (this.dataCache == null) { + this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList); + } + + this.mapData(this.dataCache, maskPattern); + }, + + setupPositionProbePattern: function(row, col) { + + for (var r = -1; r <= 7; r++) { + + if (row + r <= -1 || this.moduleCount <= row + r) continue; + + for (var c = -1; c <= 7; c++) { + + if (col + c <= -1 || this.moduleCount <= col + c) continue; + + if ((0 <= r && r <= 6 && (c == 0 || c == 6)) || + (0 <= c && c <= 6 && (r == 0 || r == 6)) || + (2 <= r && r <= 4 && 2 <= c && c <= 4)) { + this.modules[row + r][col + c] = true; + } else { + this.modules[row + r][col + c] = false; + } + } + } + }, + + getBestMaskPattern: function() { + + var minLostPoint = 0; + var pattern = 0; + + for (var i = 0; i < 8; i++) { + + this.makeImpl(true, i); + + var lostPoint = QRUtil.getLostPoint(this); + + if (i == 0 || minLostPoint > lostPoint) { + minLostPoint = lostPoint; + pattern = i; + } + } + + return pattern; + }, + + createMovieClip: function(target_mc, instance_name, depth) { + + var qr_mc = target_mc.createEmptyMovieClip(instance_name, depth); + var cs = 1; + + this.make(); + + for (var row = 0; row < this.modules.length; row++) { + + var y = row * cs; + + for (var col = 0; col < this.modules[row].length; col++) { + + var x = col * cs; + var dark = this.modules[row][col]; + + if (dark) { + qr_mc.beginFill(0, 100); + qr_mc.moveTo(x, y); + qr_mc.lineTo(x + cs, y); + qr_mc.lineTo(x + cs, y + cs); + qr_mc.lineTo(x, y + cs); + qr_mc.endFill(); + } + } + } + + return qr_mc; + }, + + setupTimingPattern: function() { + + for (var r = 8; r < this.moduleCount - 8; r++) { + if (this.modules[r][6] != null) { + continue; + } + this.modules[r][6] = (r % 2 == 0); + } + + for (var c = 8; c < this.moduleCount - 8; c++) { + if (this.modules[6][c] != null) { + continue; + } + this.modules[6][c] = (c % 2 == 0); + } + }, + + setupPositionAdjustPattern: function() { + + var pos = QRUtil.getPatternPosition(this.typeNumber); + + for (var i = 0; i < pos.length; i++) { + + for (var j = 0; j < pos.length; j++) { + + var row = pos[i]; + var col = pos[j]; + + if (this.modules[row][col] != null) { + continue; + } + + for (var r = -2; r <= 2; r++) { + + for (var c = -2; c <= 2; c++) { + + if (r == -2 || r == 2 || c == -2 || c == 2 || + (r == 0 && c == 0)) { + this.modules[row + r][col + c] = true; + } else { + this.modules[row + r][col + c] = false; + } + } + } + } + } + }, + + setupTypeNumber: function(test) { + + var bits = QRUtil.getBCHTypeNumber(this.typeNumber); + + for (var i = 0; i < 18; i++) { + var mod = (!test && ((bits >> i) & 1) == 1); + this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod; + } + + for (var i = 0; i < 18; i++) { + var mod = (!test && ((bits >> i) & 1) == 1); + this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod; + } + }, + + setupTypeInfo: function(test, maskPattern) { + + var data = (this.errorCorrectLevel << 3) | maskPattern; + var bits = QRUtil.getBCHTypeInfo(data); + + // vertical + for (var i = 0; i < 15; i++) { + + var mod = (!test && ((bits >> i) & 1) == 1); + + if (i < 6) { + this.modules[i][8] = mod; + } else if (i < 8) { + this.modules[i + 1][8] = mod; + } else { + this.modules[this.moduleCount - 15 + i][8] = mod; + } + } + + // horizontal + for (var i = 0; i < 15; i++) { + + var mod = (!test && ((bits >> i) & 1) == 1); + + if (i < 8) { + this.modules[8][this.moduleCount - i - 1] = mod; + } else if (i < 9) { + this.modules[8][15 - i - 1 + 1] = mod; + } else { + this.modules[8][15 - i - 1] = mod; + } + } + + // fixed module + this.modules[this.moduleCount - 8][8] = (!test); + + }, + + mapData: function(data, maskPattern) { + + var inc = -1; + var row = this.moduleCount - 1; + var bitIndex = 7; + var byteIndex = 0; + + for (var col = this.moduleCount - 1; col > 0; col -= 2) { + + if (col == 6) col--; + + while (true) { + + for (var c = 0; c < 2; c++) { + + if (this.modules[row][col - c] == null) { + + var dark = false; + + if (byteIndex < data.length) { + dark = (((data[byteIndex] >>> bitIndex) & 1) == 1); + } + + var mask = QRUtil.getMask(maskPattern, row, col - c); + + if (mask) { + dark = !dark; + } + + this.modules[row][col - c] = dark; + bitIndex--; + + if (bitIndex == -1) { + byteIndex++; + bitIndex = 7; + } + } + } + + row += inc; + + if (row < 0 || this.moduleCount <= row) { + row -= inc; + inc = -inc; + break; + } + } + } + + } + + }; + + QRCode.PAD0 = 0xEC; + QRCode.PAD1 = 0x11; + + QRCode.createData = function(typeNumber, errorCorrectLevel, dataList) { + + var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel); + + var buffer = new QRBitBuffer(); + + for (var i = 0; i < dataList.length; i++) { + var data = dataList[i]; + buffer.put(data.mode, 4); + buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber)); + data.write(buffer); + } + + // calc num max data. + var totalDataCount = 0; + for (var i = 0; i < rsBlocks.length; i++) { + totalDataCount += rsBlocks[i].dataCount; + } + + if (buffer.getLengthInBits() > totalDataCount * 8) { + throw new Error("code length overflow. (" + + buffer.getLengthInBits() + + ">" + + totalDataCount * 8 + + ")"); + } + + // end code + if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) { + buffer.put(0, 4); + } + + // padding + while (buffer.getLengthInBits() % 8 != 0) { + buffer.putBit(false); + } + + // padding + while (true) { + + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(QRCode.PAD0, 8); + + if (buffer.getLengthInBits() >= totalDataCount * 8) { + break; + } + buffer.put(QRCode.PAD1, 8); + } + + return QRCode.createBytes(buffer, rsBlocks); + } + + QRCode.createBytes = function(buffer, rsBlocks) { + + var offset = 0; + + var maxDcCount = 0; + var maxEcCount = 0; + + var dcdata = new Array(rsBlocks.length); + var ecdata = new Array(rsBlocks.length); + + for (var r = 0; r < rsBlocks.length; r++) { + + var dcCount = rsBlocks[r].dataCount; + var ecCount = rsBlocks[r].totalCount - dcCount; + + maxDcCount = Math.max(maxDcCount, dcCount); + maxEcCount = Math.max(maxEcCount, ecCount); + + dcdata[r] = new Array(dcCount); + + for (var i = 0; i < dcdata[r].length; i++) { + dcdata[r][i] = 0xff & buffer.buffer[i + offset]; + } + offset += dcCount; + + var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount); + var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1); + + var modPoly = rawPoly.mod(rsPoly); + ecdata[r] = new Array(rsPoly.getLength() - 1); + for (var i = 0; i < ecdata[r].length; i++) { + var modIndex = i + modPoly.getLength() - ecdata[r].length; + ecdata[r][i] = (modIndex >= 0) ? modPoly.get(modIndex) : 0; + } + + } + + var totalCodeCount = 0; + for (var i = 0; i < rsBlocks.length; i++) { + totalCodeCount += rsBlocks[i].totalCount; + } + + var data = new Array(totalCodeCount); + var index = 0; + + for (var i = 0; i < maxDcCount; i++) { + for (var r = 0; r < rsBlocks.length; r++) { + if (i < dcdata[r].length) { + data[index++] = dcdata[r][i]; + } + } + } + + for (var i = 0; i < maxEcCount; i++) { + for (var r = 0; r < rsBlocks.length; r++) { + if (i < ecdata[r].length) { + data[index++] = ecdata[r][i]; + } + } + } + + return data; + + } + + //--------------------------------------------------------------------- + // QRMode + //--------------------------------------------------------------------- + + var QRMode = { + MODE_NUMBER: 1 << 0, + MODE_ALPHA_NUM: 1 << 1, + MODE_8BIT_BYTE: 1 << 2, + MODE_KANJI: 1 << 3 + }; + + //--------------------------------------------------------------------- + // QRErrorCorrectLevel + //--------------------------------------------------------------------- + + var QRErrorCorrectLevel = { + L: 1, + M: 0, + Q: 3, + H: 2 + }; + + //--------------------------------------------------------------------- + // QRMaskPattern + //--------------------------------------------------------------------- + + var QRMaskPattern = { + PATTERN000: 0, + PATTERN001: 1, + PATTERN010: 2, + PATTERN011: 3, + PATTERN100: 4, + PATTERN101: 5, + PATTERN110: 6, + PATTERN111: 7 + }; + + //--------------------------------------------------------------------- + // QRUtil + //--------------------------------------------------------------------- + + var QRUtil = { + + PATTERN_POSITION_TABLE: [ + [], + [6, 18], + [6, 22], + [6, 26], + [6, 30], + [6, 34], + [6, 22, 38], + [6, 24, 42], + [6, 26, 46], + [6, 28, 50], + [6, 30, 54], + [6, 32, 58], + [6, 34, 62], + [6, 26, 46, 66], + [6, 26, 48, 70], + [6, 26, 50, 74], + [6, 30, 54, 78], + [6, 30, 56, 82], + [6, 30, 58, 86], + [6, 34, 62, 90], + [6, 28, 50, 72, 94], + [6, 26, 50, 74, 98], + [6, 30, 54, 78, 102], + [6, 28, 54, 80, 106], + [6, 32, 58, 84, 110], + [6, 30, 58, 86, 114], + [6, 34, 62, 90, 118], + [6, 26, 50, 74, 98, 122], + [6, 30, 54, 78, 102, 126], + [6, 26, 52, 78, 104, 130], + [6, 30, 56, 82, 108, 134], + [6, 34, 60, 86, 112, 138], + [6, 30, 58, 86, 114, 142], + [6, 34, 62, 90, 118, 146], + [6, 30, 54, 78, 102, 126, 150], + [6, 24, 50, 76, 102, 128, 154], + [6, 28, 54, 80, 106, 132, 158], + [6, 32, 58, 84, 110, 136, 162], + [6, 26, 54, 82, 110, 138, 166], + [6, 30, 58, 86, 114, 142, 170] + ], + + G15: (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0), + G18: (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0), + G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1), + + getBCHTypeInfo: function(data) { + var d = data << 10; + while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) { + d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15))); + } + return ((data << 10) | d) ^ QRUtil.G15_MASK; + }, + + getBCHTypeNumber: function(data) { + var d = data << 12; + while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) { + d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18))); + } + return (data << 12) | d; + }, + + getBCHDigit: function(data) { + + var digit = 0; + + while (data != 0) { + digit++; + data >>>= 1; + } + + return digit; + }, + + getPatternPosition: function(typeNumber) { + return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1]; + }, + + getMask: function(maskPattern, i, j) { + + switch (maskPattern) { + + case QRMaskPattern.PATTERN000: + return (i + j) % 2 == 0; + case QRMaskPattern.PATTERN001: + return i % 2 == 0; + case QRMaskPattern.PATTERN010: + return j % 3 == 0; + case QRMaskPattern.PATTERN011: + return (i + j) % 3 == 0; + case QRMaskPattern.PATTERN100: + return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0; + case QRMaskPattern.PATTERN101: + return (i * j) % 2 + (i * j) % 3 == 0; + case QRMaskPattern.PATTERN110: + return ((i * j) % 2 + (i * j) % 3) % 2 == 0; + case QRMaskPattern.PATTERN111: + return ((i * j) % 3 + (i + j) % 2) % 2 == 0; + + default: + throw new Error("bad maskPattern:" + maskPattern); + } + }, + + getErrorCorrectPolynomial: function(errorCorrectLength) { + + var a = new QRPolynomial([1], 0); + + for (var i = 0; i < errorCorrectLength; i++) { + a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0)); + } + + return a; + }, + + getLengthInBits: function(mode, type) { + + if (1 <= type && type < 10) { + + // 1 - 9 + + switch (mode) { + case QRMode.MODE_NUMBER: + return 10; + case QRMode.MODE_ALPHA_NUM: + return 9; + case QRMode.MODE_8BIT_BYTE: + return 8; + case QRMode.MODE_KANJI: + return 8; + default: + throw new Error("mode:" + mode); + } + + } else if (type < 27) { + + // 10 - 26 + + switch (mode) { + case QRMode.MODE_NUMBER: + return 12; + case QRMode.MODE_ALPHA_NUM: + return 11; + case QRMode.MODE_8BIT_BYTE: + return 16; + case QRMode.MODE_KANJI: + return 10; + default: + throw new Error("mode:" + mode); + } + + } else if (type < 41) { + + // 27 - 40 + + switch (mode) { + case QRMode.MODE_NUMBER: + return 14; + case QRMode.MODE_ALPHA_NUM: + return 13; + case QRMode.MODE_8BIT_BYTE: + return 16; + case QRMode.MODE_KANJI: + return 12; + default: + throw new Error("mode:" + mode); + } + + } else { + throw new Error("type:" + type); + } + }, + + getLostPoint: function(qrCode) { + + var moduleCount = qrCode.getModuleCount(); + + var lostPoint = 0; + + // LEVEL1 + + for (var row = 0; row < moduleCount; row++) { + + for (var col = 0; col < moduleCount; col++) { + + var sameCount = 0; + var dark = qrCode.isDark(row, col); + + for (var r = -1; r <= 1; r++) { + + if (row + r < 0 || moduleCount <= row + r) { + continue; + } + + for (var c = -1; c <= 1; c++) { + + if (col + c < 0 || moduleCount <= col + c) { + continue; + } + + if (r == 0 && c == 0) { + continue; + } + + if (dark == qrCode.isDark(row + r, col + c)) { + sameCount++; + } + } + } + + if (sameCount > 5) { + lostPoint += (3 + sameCount - 5); + } + } + } + + // LEVEL2 + + for (var row = 0; row < moduleCount - 1; row++) { + for (var col = 0; col < moduleCount - 1; col++) { + var count = 0; + if (qrCode.isDark(row, col)) count++; + if (qrCode.isDark(row + 1, col)) count++; + if (qrCode.isDark(row, col + 1)) count++; + if (qrCode.isDark(row + 1, col + 1)) count++; + if (count == 0 || count == 4) { + lostPoint += 3; + } + } + } + + // LEVEL3 + + for (var row = 0; row < moduleCount; row++) { + for (var col = 0; col < moduleCount - 6; col++) { + if (qrCode.isDark(row, col) && + !qrCode.isDark(row, col + 1) && + qrCode.isDark(row, col + 2) && + qrCode.isDark(row, col + 3) && + qrCode.isDark(row, col + 4) && + !qrCode.isDark(row, col + 5) && + qrCode.isDark(row, col + 6)) { + lostPoint += 40; + } + } + } + + for (var col = 0; col < moduleCount; col++) { + for (var row = 0; row < moduleCount - 6; row++) { + if (qrCode.isDark(row, col) && + !qrCode.isDark(row + 1, col) && + qrCode.isDark(row + 2, col) && + qrCode.isDark(row + 3, col) && + qrCode.isDark(row + 4, col) && + !qrCode.isDark(row + 5, col) && + qrCode.isDark(row + 6, col)) { + lostPoint += 40; + } + } + } + + // LEVEL4 + + var darkCount = 0; + + for (var col = 0; col < moduleCount; col++) { + for (var row = 0; row < moduleCount; row++) { + if (qrCode.isDark(row, col)) { + darkCount++; + } + } + } + + var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5; + lostPoint += ratio * 10; + + return lostPoint; + } + + }; + + + //--------------------------------------------------------------------- + // QRMath + //--------------------------------------------------------------------- + + var QRMath = { + + glog: function(n) { + + if (n < 1) { + throw new Error("glog(" + n + ")"); + } + + return QRMath.LOG_TABLE[n]; + }, + + gexp: function(n) { + + while (n < 0) { + n += 255; + } + + while (n >= 256) { + n -= 255; + } + + return QRMath.EXP_TABLE[n]; + }, + + EXP_TABLE: new Array(256), + + LOG_TABLE: new Array(256) + + }; + + for (var i = 0; i < 8; i++) { + QRMath.EXP_TABLE[i] = 1 << i; + } + for (var i = 8; i < 256; i++) { + QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^ + QRMath.EXP_TABLE[i - 5] ^ + QRMath.EXP_TABLE[i - 6] ^ + QRMath.EXP_TABLE[i - 8]; + } + for (var i = 0; i < 255; i++) { + QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i; + } + + //--------------------------------------------------------------------- + // QRPolynomial + //--------------------------------------------------------------------- + + function QRPolynomial(num, shift) { + + if (num.length == undefined) { + throw new Error(num.length + "/" + shift); + } + + var offset = 0; + + while (offset < num.length && num[offset] == 0) { + offset++; + } + + this.num = new Array(num.length - offset + shift); + for (var i = 0; i < num.length - offset; i++) { + this.num[i] = num[i + offset]; + } + } + + QRPolynomial.prototype = { + + get: function(index) { + return this.num[index]; + }, + + getLength: function() { + return this.num.length; + }, + + multiply: function(e) { + + var num = new Array(this.getLength() + e.getLength() - 1); + + for (var i = 0; i < this.getLength(); i++) { + for (var j = 0; j < e.getLength(); j++) { + num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j))); + } + } + + return new QRPolynomial(num, 0); + }, + + mod: function(e) { + + if (this.getLength() - e.getLength() < 0) { + return this; + } + + var ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0)); + + var num = new Array(this.getLength()); + + for (var i = 0; i < this.getLength(); i++) { + num[i] = this.get(i); + } + + for (var i = 0; i < e.getLength(); i++) { + num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio); + } + + // recursive call + return new QRPolynomial(num, 0).mod(e); + } + }; + + //--------------------------------------------------------------------- + // QRRSBlock + //--------------------------------------------------------------------- + + function QRRSBlock(totalCount, dataCount) { + this.totalCount = totalCount; + this.dataCount = dataCount; + } + + QRRSBlock.RS_BLOCK_TABLE = [ + + // L + // M + // Q + // H + + // 1 + [1, 26, 19], + [1, 26, 16], + [1, 26, 13], + [1, 26, 9], + + // 2 + [1, 44, 34], + [1, 44, 28], + [1, 44, 22], + [1, 44, 16], + + // 3 + [1, 70, 55], + [1, 70, 44], + [2, 35, 17], + [2, 35, 13], + + // 4 + [1, 100, 80], + [2, 50, 32], + [2, 50, 24], + [4, 25, 9], + + // 5 + [1, 134, 108], + [2, 67, 43], + [2, 33, 15, 2, 34, 16], + [2, 33, 11, 2, 34, 12], + + // 6 + [2, 86, 68], + [4, 43, 27], + [4, 43, 19], + [4, 43, 15], + + // 7 + [2, 98, 78], + [4, 49, 31], + [2, 32, 14, 4, 33, 15], + [4, 39, 13, 1, 40, 14], + + // 8 + [2, 121, 97], + [2, 60, 38, 2, 61, 39], + [4, 40, 18, 2, 41, 19], + [4, 40, 14, 2, 41, 15], + + // 9 + [2, 146, 116], + [3, 58, 36, 2, 59, 37], + [4, 36, 16, 4, 37, 17], + [4, 36, 12, 4, 37, 13], + + // 10 + [2, 86, 68, 2, 87, 69], + [4, 69, 43, 1, 70, 44], + [6, 43, 19, 2, 44, 20], + [6, 43, 15, 2, 44, 16], + + // 11 + [4, 101, 81], + [1, 80, 50, 4, 81, 51], + [4, 50, 22, 4, 51, 23], + [3, 36, 12, 8, 37, 13], + + // 12 + [2, 116, 92, 2, 117, 93], + [6, 58, 36, 2, 59, 37], + [4, 46, 20, 6, 47, 21], + [7, 42, 14, 4, 43, 15], + + // 13 + [4, 133, 107], + [8, 59, 37, 1, 60, 38], + [8, 44, 20, 4, 45, 21], + [12, 33, 11, 4, 34, 12], + + // 14 + [3, 145, 115, 1, 146, 116], + [4, 64, 40, 5, 65, 41], + [11, 36, 16, 5, 37, 17], + [11, 36, 12, 5, 37, 13], + + // 15 + [5, 109, 87, 1, 110, 88], + [5, 65, 41, 5, 66, 42], + [5, 54, 24, 7, 55, 25], + [11, 36, 12], + + // 16 + [5, 122, 98, 1, 123, 99], + [7, 73, 45, 3, 74, 46], + [15, 43, 19, 2, 44, 20], + [3, 45, 15, 13, 46, 16], + + // 17 + [1, 135, 107, 5, 136, 108], + [10, 74, 46, 1, 75, 47], + [1, 50, 22, 15, 51, 23], + [2, 42, 14, 17, 43, 15], + + // 18 + [5, 150, 120, 1, 151, 121], + [9, 69, 43, 4, 70, 44], + [17, 50, 22, 1, 51, 23], + [2, 42, 14, 19, 43, 15], + + // 19 + [3, 141, 113, 4, 142, 114], + [3, 70, 44, 11, 71, 45], + [17, 47, 21, 4, 48, 22], + [9, 39, 13, 16, 40, 14], + + // 20 + [3, 135, 107, 5, 136, 108], + [3, 67, 41, 13, 68, 42], + [15, 54, 24, 5, 55, 25], + [15, 43, 15, 10, 44, 16], + + // 21 + [4, 144, 116, 4, 145, 117], + [17, 68, 42], + [17, 50, 22, 6, 51, 23], + [19, 46, 16, 6, 47, 17], + + // 22 + [2, 139, 111, 7, 140, 112], + [17, 74, 46], + [7, 54, 24, 16, 55, 25], + [34, 37, 13], + + // 23 + [4, 151, 121, 5, 152, 122], + [4, 75, 47, 14, 76, 48], + [11, 54, 24, 14, 55, 25], + [16, 45, 15, 14, 46, 16], + + // 24 + [6, 147, 117, 4, 148, 118], + [6, 73, 45, 14, 74, 46], + [11, 54, 24, 16, 55, 25], + [30, 46, 16, 2, 47, 17], + + // 25 + [8, 132, 106, 4, 133, 107], + [8, 75, 47, 13, 76, 48], + [7, 54, 24, 22, 55, 25], + [22, 45, 15, 13, 46, 16], + + // 26 + [10, 142, 114, 2, 143, 115], + [19, 74, 46, 4, 75, 47], + [28, 50, 22, 6, 51, 23], + [33, 46, 16, 4, 47, 17], + + // 27 + [8, 152, 122, 4, 153, 123], + [22, 73, 45, 3, 74, 46], + [8, 53, 23, 26, 54, 24], + [12, 45, 15, 28, 46, 16], + + // 28 + [3, 147, 117, 10, 148, 118], + [3, 73, 45, 23, 74, 46], + [4, 54, 24, 31, 55, 25], + [11, 45, 15, 31, 46, 16], + + // 29 + [7, 146, 116, 7, 147, 117], + [21, 73, 45, 7, 74, 46], + [1, 53, 23, 37, 54, 24], + [19, 45, 15, 26, 46, 16], + + // 30 + [5, 145, 115, 10, 146, 116], + [19, 75, 47, 10, 76, 48], + [15, 54, 24, 25, 55, 25], + [23, 45, 15, 25, 46, 16], + + // 31 + [13, 145, 115, 3, 146, 116], + [2, 74, 46, 29, 75, 47], + [42, 54, 24, 1, 55, 25], + [23, 45, 15, 28, 46, 16], + + // 32 + [17, 145, 115], + [10, 74, 46, 23, 75, 47], + [10, 54, 24, 35, 55, 25], + [19, 45, 15, 35, 46, 16], + + // 33 + [17, 145, 115, 1, 146, 116], + [14, 74, 46, 21, 75, 47], + [29, 54, 24, 19, 55, 25], + [11, 45, 15, 46, 46, 16], + + // 34 + [13, 145, 115, 6, 146, 116], + [14, 74, 46, 23, 75, 47], + [44, 54, 24, 7, 55, 25], + [59, 46, 16, 1, 47, 17], + + // 35 + [12, 151, 121, 7, 152, 122], + [12, 75, 47, 26, 76, 48], + [39, 54, 24, 14, 55, 25], + [22, 45, 15, 41, 46, 16], + + // 36 + [6, 151, 121, 14, 152, 122], + [6, 75, 47, 34, 76, 48], + [46, 54, 24, 10, 55, 25], + [2, 45, 15, 64, 46, 16], + + // 37 + [17, 152, 122, 4, 153, 123], + [29, 74, 46, 14, 75, 47], + [49, 54, 24, 10, 55, 25], + [24, 45, 15, 46, 46, 16], + + // 38 + [4, 152, 122, 18, 153, 123], + [13, 74, 46, 32, 75, 47], + [48, 54, 24, 14, 55, 25], + [42, 45, 15, 32, 46, 16], + + // 39 + [20, 147, 117, 4, 148, 118], + [40, 75, 47, 7, 76, 48], + [43, 54, 24, 22, 55, 25], + [10, 45, 15, 67, 46, 16], + + // 40 + [19, 148, 118, 6, 149, 119], + [18, 75, 47, 31, 76, 48], + [34, 54, 24, 34, 55, 25], + [20, 45, 15, 61, 46, 16] + ]; + + QRRSBlock.getRSBlocks = function(typeNumber, errorCorrectLevel) { + + var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel); + + if (rsBlock == undefined) { + throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel); + } + + var length = rsBlock.length / 3; + + var list = new Array(); + + for (var i = 0; i < length; i++) { + + var count = rsBlock[i * 3 + 0]; + var totalCount = rsBlock[i * 3 + 1]; + var dataCount = rsBlock[i * 3 + 2]; + + for (var j = 0; j < count; j++) { + list.push(new QRRSBlock(totalCount, dataCount)); + } + } + + return list; + } + + QRRSBlock.getRsBlockTable = function(typeNumber, errorCorrectLevel) { + + switch (errorCorrectLevel) { + case QRErrorCorrectLevel.L: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0]; + case QRErrorCorrectLevel.M: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1]; + case QRErrorCorrectLevel.Q: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2]; + case QRErrorCorrectLevel.H: + return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3]; + default: + return undefined; + } + } + + //--------------------------------------------------------------------- + // QRBitBuffer + //--------------------------------------------------------------------- + + function QRBitBuffer() { + this.buffer = new Array(); + this.length = 0; + } + + QRBitBuffer.prototype = { + + get: function(index) { + var bufIndex = Math.floor(index / 8); + return ((this.buffer[bufIndex] >>> (7 - index % 8)) & 1) == 1; + }, + + put: function(num, length) { + for (var i = 0; i < length; i++) { + this.putBit(((num >>> (length - i - 1)) & 1) == 1); + } + }, + + getLengthInBits: function() { + return this.length; + }, + + putBit: function(bit) { + + var bufIndex = Math.floor(this.length / 8); + if (this.buffer.length <= bufIndex) { + this.buffer.push(0); + } + + if (bit) { + this.buffer[bufIndex] |= (0x80 >>> (this.length % 8)); + } + + this.length++; + } + }; + + //--------------------------------------------------------------------- + // Support Chinese + //--------------------------------------------------------------------- + function utf16To8(text) { + var result = ''; + var c; + for (var i = 0; i < text.length; i++) { + c = text.charCodeAt(i); + if (c >= 0x0001 && c <= 0x007F) { + result += text.charAt(i); + } else if (c > 0x07FF) { + result += String.fromCharCode(0xE0 | c >> 12 & 0x0F); + result += String.fromCharCode(0x80 | c >> 6 & 0x3F); + result += String.fromCharCode(0x80 | c >> 0 & 0x3F); + } else { + result += String.fromCharCode(0xC0 | c >> 6 & 0x1F); + result += String.fromCharCode(0x80 | c >> 0 & 0x3F); + } + } + return result; + } + + uQRCode = { + + errorCorrectLevel: QRErrorCorrectLevel, + + defaults: { + size: 354, + margin: 0, + backgroundColor: '#ffffff', + foregroundColor: '#000000', + fileType: 'png', // 'jpg', 'png' + errorCorrectLevel: QRErrorCorrectLevel.H, + typeNumber: -1 + }, + + make: function(options) { + return new Promise((reslove, reject) => { + var defaultOptions = { + canvasId: options.canvasId, + componentInstance: options.componentInstance, + text: options.text, + size: this.defaults.size, + margin: this.defaults.margin, + backgroundColor: this.defaults.backgroundColor, + foregroundColor: this.defaults.foregroundColor, + fileType: this.defaults.fileType, + errorCorrectLevel: this.defaults.errorCorrectLevel, + typeNumber: this.defaults.typeNumber + }; + if (options) { + for (var i in options) { + defaultOptions[i] = options[i]; + } + } + options = defaultOptions; + if (!options.canvasId) { + console.error('uQRCode: Please set canvasId!'); + return; + } + + function createCanvas() { + var qrcode = new QRCode(options.typeNumber, options.errorCorrectLevel); + qrcode.addData(utf16To8(options.text)); + qrcode.make(); + + var ctx = uni.createCanvasContext(options.canvasId, options.componentInstance); + ctx.setFillStyle(options.backgroundColor); + ctx.fillRect(0, 0, options.size, options.size); + + var tileW = (options.size - options.margin * 2) / qrcode.getModuleCount(); + var tileH = tileW; + + for (var row = 0; row < qrcode.getModuleCount(); row++) { + for (var col = 0; col < qrcode.getModuleCount(); col++) { + var style = qrcode.isDark(row, col) ? options.foregroundColor : options.backgroundColor; + ctx.setFillStyle(style); + var x = Math.round(col * tileW) + options.margin; + var y = Math.round(row * tileH) + options.margin; + var w = Math.ceil((col + 1) * tileW) - Math.floor(col * tileW); + var h = Math.ceil((row + 1) * tileW) - Math.floor(row * tileW); + ctx.fillRect(x, y, w, h); + } + } + + setTimeout(function() { + ctx.draw(false, (function() { + setTimeout(function() { + uni.canvasToTempFilePath({ + canvasId: options.canvasId, + fileType: options.fileType, + width: options.size, + height: options.size, + destWidth: options.size, + destHeight: options.size, + success: function(res) { + let resData; // 将统一为base64格式 + let tempFilePath = res.tempFilePath; // H5为base64,其他为相对路径 + + // #ifdef H5 + resData = tempFilePath; + options.success && options.success(resData); + reslove(resData); + // #endif + + // #ifdef APP-PLUS + const path = plus.io.convertLocalFileSystemURL(tempFilePath) // 绝对路径 + let fileReader = new plus.io.FileReader(); + fileReader.readAsDataURL(path); + fileReader.onloadend = res => { + resData = res.target.result; + options.success && options.success(resData); + reslove(resData); + }; + // #endif + + // #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO + uni.getFileSystemManager().readFile({ + filePath: tempFilePath, + encoding: 'base64', + success: res => { + resData = 'data:image/png;base64,' + res.data; + options.success && options.success(resData); + reslove(resData); + } + }) + // #endif + + // #ifndef H5 || APP-PLUS || MP-WEIXIN || MP-QQ || MP-TOUTIAO + if (plus) { + const path = plus.io.convertLocalFileSystemURL(tempFilePath) // 绝对路径 + let fileReader = new plus.io.FileReader(); + fileReader.readAsDataURL(path); + fileReader.onloadend = res => { + resData = res.target.result; + options.success && options.success(resData); + reslove(resData); + }; + } else { + uni.request({ + url: tempFilePath, + method: 'GET', + responseType: 'arraybuffer', + success: res => { + resData = `data:image/png;base64,${uni.arrayBufferToBase64(res.data)}`; // 把arraybuffer转成base64 + options.success && options.success(resData); + reslove(resData); + } + }) + } + // #endif + }, + fail: function(error) { + options.fail && options.fail(error); + reject(error); + }, + complete: function(res) { + options.complete && options.complete(res); + } + }, options.componentInstance); + }, options.text.length + 100); + })()); + }, 150); + } + + createCanvas(); + }); + } + } + +})() + +export default uQRCode diff --git a/components/cloud-image/cloud-image.vue b/components/cloud-image/cloud-image.vue new file mode 100644 index 0000000..3f28159 --- /dev/null +++ b/components/cloud-image/cloud-image.vue @@ -0,0 +1,70 @@ + + + \ No newline at end of file diff --git a/components/refreshBox/refreshBox.nvue b/components/refreshBox/refreshBox.nvue new file mode 100644 index 0000000..f41c25c --- /dev/null +++ b/components/refreshBox/refreshBox.nvue @@ -0,0 +1,95 @@ + + + + diff --git a/components/uni-agreements/uni-agreements.vue b/components/uni-agreements/uni-agreements.vue new file mode 100644 index 0000000..2c0af79 --- /dev/null +++ b/components/uni-agreements/uni-agreements.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/components/uni-bindMobileByMpWeixin/uni-bindMobileByMpWeixin.vue b/components/uni-bindMobileByMpWeixin/uni-bindMobileByMpWeixin.vue new file mode 100644 index 0000000..bceda44 --- /dev/null +++ b/components/uni-bindMobileByMpWeixin/uni-bindMobileByMpWeixin.vue @@ -0,0 +1,189 @@ + + + + + diff --git a/components/uni-load-state/i18n/en.json b/components/uni-load-state/i18n/en.json new file mode 100644 index 0000000..b600ad4 --- /dev/null +++ b/components/uni-load-state/i18n/en.json @@ -0,0 +1,6 @@ +{ + "noData": "No Data", + "noNetwork": "Network error", + "toSet": "Go to settings", + "error": "error" +} diff --git a/components/uni-load-state/i18n/index.js b/components/uni-load-state/i18n/index.js new file mode 100644 index 0000000..c70a386 --- /dev/null +++ b/components/uni-load-state/i18n/index.js @@ -0,0 +1,6 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +export default { + en, + 'zh-Hans': zhHans +} diff --git a/components/uni-load-state/i18n/zh-Hans.json b/components/uni-load-state/i18n/zh-Hans.json new file mode 100644 index 0000000..4fa8e1a --- /dev/null +++ b/components/uni-load-state/i18n/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "noData": "暂无数据", + "noNetwork": "网络异常", + "toSet": "前往设置", + "error": "错误" +} diff --git a/components/uni-load-state/readme.md b/components/uni-load-state/readme.md new file mode 100644 index 0000000..07288d6 --- /dev/null +++ b/components/uni-load-state/readme.md @@ -0,0 +1,3 @@ +新增uni-load-state组件,这是一个封装数据请求状态的组件。根据uniCloud-db组件提供的参数直接响应对应的效果。 +包括加载中、当前页面为空、没有更多数据、上拉加载更多; +加载错误判断,如果是断网就引导打开系统网络设置页面。恢复联网后自动触发networkResume方法。 \ No newline at end of file diff --git a/components/uni-load-state/uni-load-state.vue b/components/uni-load-state/uni-load-state.vue new file mode 100644 index 0000000..640f4a7 --- /dev/null +++ b/components/uni-load-state/uni-load-state.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/components/uni-quick-login/uni-quick-login.vue b/components/uni-quick-login/uni-quick-login.vue new file mode 100644 index 0000000..bfc75b2 --- /dev/null +++ b/components/uni-quick-login/uni-quick-login.vue @@ -0,0 +1,396 @@ + + + + \ No newline at end of file diff --git a/components/uni-section/uni-section.vue b/components/uni-section/uni-section.vue new file mode 100644 index 0000000..b3fc47c --- /dev/null +++ b/components/uni-section/uni-section.vue @@ -0,0 +1,139 @@ + + + + diff --git a/components/uni-send-sms-code/uni-send-sms-code.vue b/components/uni-send-sms-code/uni-send-sms-code.vue new file mode 100644 index 0000000..4b59b75 --- /dev/null +++ b/components/uni-send-sms-code/uni-send-sms-code.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/components/uni-user-profile/uni-user-profile.vue b/components/uni-user-profile/uni-user-profile.vue new file mode 100644 index 0000000..62beb86 --- /dev/null +++ b/components/uni-user-profile/uni-user-profile.vue @@ -0,0 +1,194 @@ + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..bb56451 --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/lang/en.js b/lang/en.js new file mode 100644 index 0000000..43e1bfb --- /dev/null +++ b/lang/en.js @@ -0,0 +1,199 @@ +export default { + tabbar:'List,Grid,contacts,Mine', + agreementsTitle:'User service agreement,Privacy policy', + common: { + wechatFriends: "friends", + wechatBbs: "bbs", + weibo: "weibo", + more: "more", + agree:"agree", + copy: "copy", + wechatApplet: "applet", + cancelShare: "cancel sharing", + updateSucceeded: "update succeeded", + phonePlaceholder: "Please enter your mobile phone number", + verifyCodePlaceholder: "Please enter the verification code", + newPasswordPlaceholder: "Please enter a new password", + confirmNewPasswordPlaceholder: "Please confirm the new password", + confirmPassword: "Please confirm the password", + verifyCodeSend: "Verification code has been sent to via SMS", + passwordDigits: "The password is 6 - 20 digits", + getVerifyCode: "Get Code", + noAgree: "You have not agreed to the privacy policy agreement", + gotIt: "got it", + login: "sign in", + error: "error", + complete: "complete", + submit: "Submit", + formatErr: "Incorrect mobile phone number format", + sixDigitCode: "Please enter a 6-digit verification code", + resetNavTitle:"Reset password" + }, + list: { + inputPlaceholder: "Please enter the search content", + }, + search: { + cancelText: "cancel", + searchHistory: "search history", + searchDiscovery: "search discovery", + deleteAll: "delete all", + delete: "delete", + deleteTip: "Are you sure to clear the search history ?", + complete: "complete", + searchHiddenTip: "Current search found hidden", + }, + grid: { + grid: "Grid Assembly", + visibleToAll: "Visible to all", + invisibleToTourists: "Invisible to tourists", + adminVisible: "Admin visible", + clickTip: "Click the", + clickTipGrid: "grid", + }, + mine: { + showText: "Text", + signIn: "Check In Reward", + toEvaluate: "To Evaluate", + readArticles: "Read Articles", + myScore: "My Score", + invite: "Invite Friends", + guestBook: "Guest Book Example", + feedback: "Problems And Feedback", + settings: "Settings", + about: "About", + checkUpdate: "Check for Updates", + clicked: "You Clicked", + checkScore: "Please check your points after logging in", + currentScore: "The current score is ", + noScore: "There are currently no points", + notLogged: "not logged in", + }, + userinfo: { + navigationBarTitle:"My Profile", + ProfilePhoto: "Profile Photo", + nickname: "Nickname", + notSet: "not set", + phoneNumber: "Phone Number", + notSpecified: "Not Specified", + setNickname: "Set Nickname ", + setNicknamePlaceholder: "Please enter a nickname to set", + bindPhoneNumber: "One click binding of local number", + bindOtherLogin: "Other number binding", + noChange: "No change", + uploading: "uploading", + requestFail: "Request for service failed", + setting: "setting", + deleteSucceeded: "Delete succeeded", + setSucceeded: "Set successfully", + }, + smsCode: { + resendVerifyCode: "resend", + phoneErrTip: "Mobile phone number format error", + sendSuccessTip: "SMS verification code sent successfully", + }, + loadMore: { + noData: "No Data", + noNetwork: "Network error", + toSet: "Go to settings", + error: "error", + }, + guestbook: { + navigationBarTitle:"Message board", + msgContent: "post message content", + notAvailable: "not available for visitors who are not logged in", + send: "send", + addSucceeded: "Successfully added", + noPermission: "You don't have permission for this operation", + }, + uniFeedback: { + navigationBarTitle: "Problems and feedback", + msgTitle: "Message content", + imgTitle: "Picture list", + contacts: "contacts", + phone: "contact number", + submit: "submit", + }, + settings: { + navigationBarTitle:"Settings", + userInfo: "Personal Data", + changePassword: "change password", + clearTmp: "clean cache", + pushServer: "push function", + fingerPrint: "fingerprint unlock", + facial: "face unlock", + deactivate: "Deactivate", + logOut: "Logout", + login: "Login", + changeLanguage: "Language", + please: "please", + successText: "success", + failTip: "Authentication failed. Please try again", + authFailed: "authentication failed", + deviceNoOpen: "The device is not turned on", + fail: "fail", + tips: "tips", + exitLogin: "Do you want to log out?", + cancelText: "cancel", + confirmText: "confirm", + clearing: "clearing", + clearedSuccessed: "Cleared successfully", + }, + deactivate: { + cancelText: "cancel", + nextStep: "next step", + navigationBarTitle:"Logout prompt" + }, + about: { + sacnQR: "Scan the QR Code and your friends can also download it", + client: "applCantion", + and: "And", + about: "About", + }, + invite: { + download: "Download", + }, + login: { + phoneLogin: "After logging in, you can show yourself", + phoneLoginTip: "Unregistered mobile phone numbers will be automatically registered after verification", + getVerifyCode: "Get Code", + }, + uniQuickLogin: { + accountLogin: "Account", + SMSLogin: "SMS", + wechatLogin: "wechat", + appleLogin: "Apple", + oneClickLogin: "One click login", + QQLogin: "QQ", + xiaomiLogin: "Xiaomi", + getProviderFail: "Failed to get service provider", + loginErr: "Login service initialization error", + chooseOtherLogin: "Click the third-party login", + }, + pwdLogin: { + pwdLogin: "User name password login", + placeholder: "Please enter mobile number / user name", + passwordPlaceholder: "Please input a password", + verifyCodePlaceholder: "Please enter the verification code", + login: "sign in", + forgetPassword: "Forget password", + register: "Registered account", + }, + register: { + navigationBarTitle:"register", + usernamePlaceholder: "Please enter user name", + nicknamePlaceholder: "Please enter user nickname", + passwordDigitsPlaceholder: "Please enter a 6-20 digit password", + passwordAgain: "Enter the password again", + registerAndLogin: "Register and log in", + }, + listDetail: { + follow: "Click follow", + newsErr: "Error, news ID is empty", + }, + newsLog:{ + navigationBarTitle:"Reading Log" + }, + bindMobile:{ + navigationBarTitle:"Bind Mobile" + } +} diff --git a/lang/i18n.js b/lang/i18n.js new file mode 100644 index 0000000..2a35990 --- /dev/null +++ b/lang/i18n.js @@ -0,0 +1,102 @@ +import langEn from './en' +import zhHans from './zh-Hans' +import uniStarterConfig from '../uni-starter.config.js' +const {i18n:{enable:i18nEnable} }= uniStarterConfig +const messages = { + 'en': langEn, + 'zh-Hans': zhHans +} +let currentLang +if(i18nEnable){ + currentLang = uni.getStorageSync('CURRENT_LANG') +}else{ + currentLang = "zh-Hans" +} +// console.log(uni.getStorageSync('CURRENT_LANG'),currentLang); +if (!currentLang) { + if (uni.getLocale) { + console.log('获取应用语言:', uni.getLocale()); + let language = 'en' + if (uni.getLocale() != 'en') { + language = 'zh-Hans' + } + uni.setStorageSync('CURRENT_LANG', language) + currentLang = language + } else { + uni.getSystemInfo({ + success: function(res) { + console.log('获取设备信息:', res); + let language = 'zh-Hans' + if (res.language == 'en') { + language = 'en' + } + uni.setStorageSync('CURRENT_LANG', language) + currentLang = language + }, + fail: (err) => { + console.error(err) + } + }) + } +} +let i18nConfig = { + locale: currentLang, // set locale + messages // set locale messages +} + +// #ifdef VUE2 +import Vue from 'vue' +import VueI18n from 'vue-i18n' +Vue.use(VueI18n) +const i18n = new VueI18n(i18nConfig) +// #endif + +// #ifdef VUE3 +import { + createI18n +} from 'vue-i18n' +const i18n = createI18n(i18nConfig) +// #endif + +export default i18n + + +if(i18nEnable){ +console.log(` + 你已开启多语言国际化,将自动根据语言获取【lang/en.js】或【lang/en.js】文件中配置的tabbar的值, + 覆盖你在pages.json中的tabbar的值 + 如果你不需要多语言国际化,请打开配置文件uni-starter.config.js找到 -> i18n -> enable把值设置为false +`); + let initLanguageAfter = () => { + function $i18n(e){ + // #ifdef VUE3 + return i18n.global.messages[i18n.global.locale][e] + // #endif + return i18n.messages[i18n.locale][e] + } + setTimeout(function(){ + //底部tabbar更新 + $i18n('tabbar').split(',').forEach((text, index) => { + // console.log(text); + uni.setTabBarItem({ + index, + text, + complete: e => { + // console.log("e: " + JSON.stringify(e)); + } + }) + }) + },1) + //更新 uni-starter.config agreements + let agreementsTitle = $i18n('agreementsTitle').split(',') + let agreements = uniStarterConfig.about.agreements + agreements[0].title = agreementsTitle[0] + agreements[1].title = agreementsTitle[1] + uniStarterConfig.about.agreements = agreements + } + initLanguageAfter() + uni.$on('changeLanguage', e => { + console.log('changeLanguage', e); + initLanguageAfter(e) + }) +} \ No newline at end of file diff --git a/lang/zh-Hans.js b/lang/zh-Hans.js new file mode 100644 index 0000000..7cbcb56 --- /dev/null +++ b/lang/zh-Hans.js @@ -0,0 +1,200 @@ +export default { + tabbar:'列表,宫格,通讯录,我的', + agreementsTitle:'用户服务协议,隐私政策', + common:{ + wechatFriends: "微信好友", + wechatBbs: "微信朋友圈", + weibo:"微博", + more: "更多", + agree:"同意", + copy: "复制", + wechatApplet: "微信小程序", + cancelShare: "取消分享", + updateSucceeded: "更新成功", + phonePlaceholder: "请输入手机号", + verifyCodePlaceholder: "请输入验证码", + newPasswordPlaceholder: "请输入新密码", + confirmNewPasswordPlaceholder: "请确认新密码", + confirmPassword: "请确认密码", + verifyCodeSend: "验证码已通过短信发送至", + passwordDigits: "密码为6 - 20位", + getVerifyCode: "获取验证码", + noAgree: "你未同意隐私政策协议", + gotIt: "知道了", + login: "登录", + error: "错误", + complete: "完成", + submit: "提交", + formatErr:"手机号码格式不正确", + sixDigitCode:"请输入6位验证码", + resetNavTitle:"重置密码" + + }, + list: { + inputPlaceholder: "请输入搜索内容", + }, + search:{ + cancelText: '取消', + searchHistory: "搜索历史", + searchDiscovery: "搜索发现", + deleteAll: "全部删除", + delete: "删除", + deleteTip: "确认清空搜索历史吗?", + complete: "完成", + searchHiddenTip: "当前搜索发现已隐藏", + }, + grid:{ + grid: "宫格组件", + visibleToAll: "所有人可见", + invisibleToTourists: "游客不可见", + adminVisible: "管理员可见", + clickTip: "点击第", + clickTipGrid: "个宫格", + }, + mine:{ + showText: "文字", + signIn: "签到有奖", + toEvaluate: "去评分", + readArticles: "阅读过的文章", + myScore: "我的积分", + invite: "分销推荐", + guestBook: "留言板示例", + feedback: "问题与反馈", + settings: "设置", + checkUpdate: "检查更新", + about: "关于", + clicked: "你点击了", + checkScore: "请登录后查看积分", + currentScore: "当前积分为", + noScore: "当前无积分", + notLogged: "未登录", + }, + userinfo:{ + navigationBarTitle:"个人资料", + ProfilePhoto: "头像", + nickname: "昵称", + notSet: "未设置", + phoneNumber: "手机号", + notSpecified: "未绑定", + setNickname: "设置昵称", + setNicknamePlaceholder: "请输入要设置的昵称", + bindPhoneNumber: "本机号码一键绑定", + bindOtherLogin: "其他号码绑定", + noChange: "没有变化", + uploading: "正在上传", + requestFail: "请求服务失败", + setting: "设置中", + deleteSucceeded: "删除成功", + setSucceeded: "设置成功", + }, + smsCode:{ + resendVerifyCode: "重新发送", + phoneErrTip: "手机号格式错误", + sendSuccessTip: "短信验证码发送成功", + }, + loadMore:{ + noData: "暂无数据", + noNetwork: "网络异常", + toSet: "前往设置", + error: "错误", + }, + guestbook:{ + navigationBarTitle:"留言板", + msgContent: "发表留言内容", + notAvailable: "未登录游客不可用", + send: "发送", + addSucceeded: "新增成功", + noPermission: "你没有该操作权限", + }, + uniFeedback:{ + navigationBarTitle:"问题与反馈", + msgTitle: "留言内容", + imgTitle: "图片列表", + contacts: "联系人", + phone: "联系电话", + submit: "提交", + }, + settings:{ + navigationBarTitle:"设置", + userInfo: "个人资料", + changePassword: "修改密码", + clearTmp: "清理缓存", + pushServer: "推送功能", + fingerPrint: "指纹解锁", + facial: "人脸解锁", + deactivate: "注销账号", + logOut: "退出登录", + login: "登录", + failTip: "认证失败请重试", + authFailed: "认证失败", + changeLanguage: "切换语言", + please: "请用", + successText: "成功", + deviceNoOpen: "设备未开启", + fail: "失败", + tips: "提示", + exitLogin: "是否退出登录?", + clearing: "清除中", + clearedSuccessed: "清除成功", + confirmText: "确定", + cancelText: '取消', + }, + deactivate:{ + cancelText: '取消', + nextStep: "下一步", + navigationBarTitle:"注销提示" + }, + about:{ + sacnQR: "扫描二维码,您的朋友也可以下载", + client: "客户端", + and: "和", + about: "关于", + }, + invite:{ + download: "下载", + }, + login:{ + phoneLogin: "登录后即可展示自己", + phoneLoginTip: "未注册的手机号验证通过后将自动注册", + getVerifyCode: "获取验证码", + }, + uniQuickLogin:{ + accountLogin: "账号登录", + SMSLogin: "短信验证码", + wechatLogin: "微信登录", + appleLogin: "苹果登录", + oneClickLogin: "一键登录", + QQLogin: "QQ登录", + xiaomiLogin: "小米登录", + getProviderFail: "获取服务供应商失败", + loginErr: "登录服务初始化错误", + chooseOtherLogin: "点击了第三方登录", + }, + pwdLogin:{ + pwdLogin: "用户名密码登录", + placeholder: "请输入手机号/用户名", + passwordPlaceholder: "请输入密码", + verifyCodePlaceholder: "请输入验证码", + login: "登录", + forgetPassword: "忘记密码", + register: "注册账号", + }, + register:{ + navigationBarTitle:"注册", + usernamePlaceholder: "请输入用户名", + nicknamePlaceholder: "请输入用户昵称", + registerAndLogin: "注册并登录", + passwordDigitsPlaceholder: "请输入6-20位密码", + passwordAgain: "再次输入密码", + }, + listDetail:{ + follow: "点击关注", + newsErr: "出错了,新闻ID为空", + }, + newsLog:{ + navigationBarTitle:"阅读记录" + }, + bindMobile:{ + navigationBarTitle:"绑定手机号码" + } +} diff --git a/main.js b/main.js new file mode 100644 index 0000000..a0b835c --- /dev/null +++ b/main.js @@ -0,0 +1,29 @@ +import App from './App' +import store from './store' +import i18n from './lang/i18n' + + +// #ifndef VUE3 +import Vue from 'vue' +Vue.config.productionTip = false +Vue.prototype.$store = store +App.mpType = 'app' +const app = new Vue({ + i18n, + store, + ...App +}) +app.$mount() +// #endif + + +// #ifdef VUE3 +import {createSSRApp} from 'vue' + +export function createApp() { + const app = createSSRApp(App) + app.use(i18n) + app.use(store) + return {app} +} +// #endif diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..0c95e9f --- /dev/null +++ b/manifest.json @@ -0,0 +1,229 @@ +{ + "name": "统一应用基本项目", + "appid": "请点击重新获取", + "description": "云端一体应用快速开发模版", + "versionName": "1.0.0", + "versionCode": "100", + "transformPx": false, + "app-plus": { + "locales": { + "en": { + // 英文 + "name": "uni-starter", // 应用名称 + "android": { + "strings": { + //Android平台自定义字符串 + "CustomKey": "CustomValue" + } + }, + "ios": { + "privacyDescription": { + //iOS平台隐私访问描述信息 + "NSPhotoLibraryUsageDescription": "access to the user’s photo library(read)" + }, + "infoPlist": { + //iOS平台自定义InfoPlist.strings + "CustomKey": "CustomValue" + } + } + }, + "zh": { + // 中文(简体) + "name": "统一应用基本项目" // 应用名称 + } + }, + "privacy": { + "prompt": "template", + "template": { + "title": "服务协议和隐私政策", + "message": "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。
  你可阅读《服务协议》《隐私政策》了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。", + "buttonAccept": "同意", + "buttonRefuse": "暂不同意" + } + }, + "compatible": { + "ignoreVersion": true + }, + "usingComponents": true, + "nvueStyleCompiler": "uni-app", + "compilerVersion": 3, + "splashscreen": { + "alwaysShowBeforeRender": false, + "waiting": true, + "autoclose": true, + "delay": 0 + }, + "modules": { + "Fingerprint": { + }, + "Share": { + }, + "OAuth": { + }, + "FaceID": { + }, + "Geolocation": { + }, + "Push": { + }, + "Bluetooth": { + } + }, + "distribute": { + "android": { + "permissions": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "abiFilters": [ + "armeabi-v7a", + "arm64-v8a", + "x86" + ] + }, + "ios": { + "capabilities": { + "entitlements": { + "com.apple.developer.associated-domains": [ + "applinks:static-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e.bspapp.com" + ] + } + } + }, + "sdkConfigs": { + "oauth": { + "apple": { + }, + "weixin": { + "appid": "", + "appsecret": "", + "UniversalLinks": "" + }, + "univerify": { + } + }, + "ad": { + }, + "share": { + "weixin": { + "appid": "", + "UniversalLinks": "" + } + }, + "geolocation": { + "baidu": { + "__platform__": [ + "ios", + "android" + ], + "appkey_ios": "请填写地图的key", + "appkey_android": "请填写地图的key" + } + }, + "push": { + "unipush": { + } + }, + "payment": { + } + }, + "icons": { + "android": { + "hdpi": "", + "xhdpi": "", + "xxhdpi": "", + "xxxhdpi": "" + }, + "ios": { + "appstore": "", + "ipad": { + "app": "", + "app@2x": "", + "notification": "", + "notification@2x": "", + "proapp@2x": "", + "settings": "", + "settings@2x": "", + "spotlight": "", + "spotlight@2x": "" + }, + "iphone": { + "app@2x": "", + "app@3x": "", + "notification@2x": "", + "notification@3x": "", + "settings@2x": "", + "settings@3x": "", + "spotlight@2x": "", + "spotlight@3x": "" + } + } + }, + "splashscreen": { + "iosStyle": "common", + "androidStyle": "common" + } + }, + "nvueLaunchMode": "" + }, + "quickapp": { + }, + "mp-weixin": { + "appid": "", + "setting": { + "urlCheck": false, + "es6": false + }, + "usingComponents": true, + "betterScopedSlots": true, + "permission": { + "scope.userLocation": { + "desc": "演示在onShow生命周期获取地理位置" + } + } + }, + "mp-alipay": { + "usingComponents": true + }, + "mp-baidu": { + "usingComponents": true + }, + "mp-toutiao": { + "usingComponents": true + }, + "uniStatistics": { + "enable": false + }, + "h5": { + "template": "", + "sdkConfigs": { + "maps": { + "qqmap": { + "key": "" + } + } + }, + "router": { + "base": "" + } + }, + "_spaceID": "", + "vueVersion": "2" +} +//... +// 中文(简体) +// 应用名称 diff --git a/package.json b/package.json new file mode 100644 index 0000000..c0099ed --- /dev/null +++ b/package.json @@ -0,0 +1,85 @@ +{ + "id": "uni-starter", + "displayName": "uni-starter", + "version": "1.1.22", + "description": "云端一体应用快速开发基本项目模版", + "keywords": [ + "login", + "登录", + "搜索", + "uni-id例子", + "留言板" + ], + "repository": "https://codechina.csdn.net/dcloud/uni-starter.git", + "engines": { + "HBuilderX": "^3.2.6" + }, + "dcloudext": { + "category": [ + "uniCloud", + "云端一体项目模板" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "u", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "y", + "IE": "n", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + }, + "dependencies": {} +} diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..ebd0830 --- /dev/null +++ b/pages.json @@ -0,0 +1,240 @@ +{ + "pages": [{ + "path": "pages/list/list", + "style": { + // #ifndef APP-PLUS + "enablePullDownRefresh": true, + // #endif + "navigationStyle": "custom" + } + }, + { + "path": "pages/grid/grid", + "style": { + //#ifndef MP + "navigationStyle": "custom" + //#endif + } + }, { + "path": "pages/ucenter/login-page/index/index", + "style": { + "navigationBarTitleText": "", + "app-plus": { + "animationType": "none", + "popGesture": "none" + } + } + }, { + "path": "pages/list/search/search", + "style": { + "navigationBarTitleText":"搜索" + } + }, { + "path": "pages/list/detail", + "style": { + "app-plus": { + "titleNView": { + "buttons": [{ + "type": "share" + }], + "type": "transparent" + } + }, + "h5": { + "titleNView": { + "type": "transparent" + } + }, + "navigationBarTitleText": "文章详情" + } + }, { + "path": "pages/ucenter/userinfo/bind-mobile/bind-mobile", + "style": { + "navigationBarTitleText": "绑定手机号码" + } + }, + { + "path": "pages/ucenter/ucenter", + "style": { + "navigationStyle": "custom" + } + }, { + "path": "pages/ucenter/about/about", + "style": { + "navigationBarTitleText": "关于" + // #ifdef APP-PLUS + , + "app-plus": { + "titleNView": { + "buttons": [{ + "type": "share" + }] + } + } + // #endif + } + + }, + { + "path": "uni_modules/uni-upgrade-center-app/pages/upgrade-popup", + "style": { + "disableScroll": true, + "app-plus": { + "backgroundColorTop": "transparent", + "background": "transparent", + "titleNView": false, + "scrollIndicator": false, + "popGesture": "none", + "animationType": "fade-in", + "animationDuration": 200 + + } + } + }, { + "path": "pages/uni-agree/uni-agree", + "style": { + "navigationStyle": "custom", + "app-plus": { + "popGesture": "none" + } + } + }, { + "path": "pages/ucenter/settings/settings", + "style": { + "navigationBarTitleText": "设置" + } + + }, { + "path": "pages/ucenter/userinfo/userinfo", + "style": { + "navigationBarTitleText": "个人资料" + } + }, { + "path": "pages/ucenter/userinfo/cropImage", + "style": { + "navigationStyle": "custom" + } + }, { + "path": "pages/ucenter/login-page/pwd-login/pwd-login", + "style": { + "navigationBarTitleText": "" + } + }, { + "path": "pages/ucenter/login-page/pwd-retrieve/pwd-retrieve", + "style": { + "navigationBarTitleText": "重置密码" + } + }, { + "path": "pages/ucenter/login-page/phone-code/phone-code", + "style": { + "navigationBarTitleText": "" + } + + }, { + "path": "pages/common/webview/webview", + "style": { + "navigationBarTitleText": "", + "enablePullDownRefresh": false + } + + }, { + "path": "pages/ucenter/login-page/register/register", + "style": { + "navigationBarTitleText": "注册", + "enablePullDownRefresh": false + } + }, + { + "path": "pages/ucenter/read-news-log/read-news-log", + "style": { + "navigationBarTitleText": "阅读记录", + "enablePullDownRefresh": true + } + }, { + "path": "pages/ucenter/invite/invite", + "style": { + "navigationStyle": "custom", + "enablePullDownRefresh": false + } + + }, { + "path": "pages/ucenter/settings/deactivate/deactivate", + "style": { + "navigationBarTitleText": "注销提醒", + "enablePullDownRefresh": false + } + }, { + "path": "uni_modules/uni-feedback/pages/opendb-feedback/opendb-feedback", + "style": { + "navigationBarTitleText": "意见反馈", + "enablePullDownRefresh": false + } + }, { + "path": "pages/ucenter/guestbook/guestbook", + "style": { + "navigationBarTitleText": "留言板", + "enablePullDownRefresh": false, + "app-plus": { + "titleNView": { + "buttons": [{ + "text": "切换", + "fontSize": "12px" + }, + { + "text": "注销", + "fontSize": "12px" + } + ] + } + } + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "uni-starter", + "navigationBarBackgroundColor": "#FFFFFF", + "backgroundColor": "#F8F8F8", + "enablePullDownRefresh": false + }, + "condition": { + "list": [{ + "path": "pages/list/detail" + }, { + "path": "pages/list/list" + }, + { + "path": "pages/ucenter/login-page/index/index" + }, { + "path": "pages/ucenter/userinfo/userinfo" + }, + { + "path": "pages/ucenter/settings/settings" + } + ], + "current": 1 + }, + "tabBar": { + "color": "#7A7E83", + "selectedColor": "#007AFF", + "borderStyle": "black", + "backgroundColor": "#FFFFFF", + "list": [{ + "pagePath": "pages/list/list", + "iconPath": "static/tabbar/list.png", + "selectedIconPath": "static/tabbar/list_active.png", + "text": "列表" + }, { + "pagePath": "pages/grid/grid", + "iconPath": "static/tabbar/grid.png", + "selectedIconPath": "static/tabbar/grid_active.png", + "text": "宫格" + } + , { + "pagePath": "pages/ucenter/ucenter", + "iconPath": "static/tabbar/me.png", + "selectedIconPath": "static/tabbar/me_active.png", + "text": "我的" + }] + } +} diff --git a/pages/common/webview/webview.vue b/pages/common/webview/webview.vue new file mode 100644 index 0000000..5fa64b9 --- /dev/null +++ b/pages/common/webview/webview.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/pages/grid/grid.vue b/pages/grid/grid.vue new file mode 100644 index 0000000..0358fbc --- /dev/null +++ b/pages/grid/grid.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/pages/list/detail.vue b/pages/list/detail.vue new file mode 100644 index 0000000..ee1ca77 --- /dev/null +++ b/pages/list/detail.vue @@ -0,0 +1,368 @@ + + + + + diff --git a/pages/list/list.nvue b/pages/list/list.nvue new file mode 100644 index 0000000..dec5cb4 --- /dev/null +++ b/pages/list/list.nvue @@ -0,0 +1,238 @@ + + + + + diff --git a/pages/list/search/search.nvue b/pages/list/search/search.nvue new file mode 100644 index 0000000..12ab770 --- /dev/null +++ b/pages/list/search/search.nvue @@ -0,0 +1,506 @@ + + + + + + + diff --git a/pages/ucenter/about/about.vue b/pages/ucenter/about/about.vue new file mode 100644 index 0000000..beab4a5 --- /dev/null +++ b/pages/ucenter/about/about.vue @@ -0,0 +1,203 @@ + + + diff --git a/pages/ucenter/guestbook/guestbook.vue b/pages/ucenter/guestbook/guestbook.vue new file mode 100644 index 0000000..0edc841 --- /dev/null +++ b/pages/ucenter/guestbook/guestbook.vue @@ -0,0 +1,223 @@ + + + + + \ No newline at end of file diff --git a/pages/ucenter/invite/invite.vue b/pages/ucenter/invite/invite.vue new file mode 100644 index 0000000..5bb4df8 --- /dev/null +++ b/pages/ucenter/invite/invite.vue @@ -0,0 +1,179 @@ + + + diff --git a/pages/ucenter/login-page/common/login-page.css b/pages/ucenter/login-page/common/login-page.css new file mode 100644 index 0000000..34ff42c --- /dev/null +++ b/pages/ucenter/login-page/common/login-page.css @@ -0,0 +1,61 @@ +/* #ifndef APP-NVUE */ +view { + display: flex; + box-sizing: border-box; + flex-direction: column; +} + +/* #endif */ + +.content { + padding: 0 50rpx; + width: 750rpx; + flex: 1; +} + +.input-box { + padding: 0 15px; + margin-bottom: 10px; + background-color: #F8F8F8; + border-radius: 6px; + font-size: 28rpx; +} + +.get-code { + margin: 0; + margin-top: 15px; + background-color: #007aff; + color: #FFFFFF; +} + +.input-box, +.get-code { + height: 45px; + line-height: 45px; +} + +.title { + text-align: center; + padding-bottom: 5px; +} + +.tip { + color: #666666; + font-size: 26rpx; + margin: 6px 0; +} + +.easyinput { + background-color: #F8F8F8; + border-radius: 6rpx; +} + +.send-btn { + width: 100%; + margin-top: 15px; + border-radius: 6rpx; +} + +.link { + color: #04498c; +} diff --git a/pages/ucenter/login-page/common/login-page.mixin.js b/pages/ucenter/login-page/common/login-page.mixin.js new file mode 100644 index 0000000..6b16125 --- /dev/null +++ b/pages/ucenter/login-page/common/login-page.mixin.js @@ -0,0 +1,15 @@ +import {mapMutations} from 'vuex'; +import loginSuccess from './loginSuccess.js'; +let mixin = { + methods:{ + ...mapMutations({ + setUserInfo: 'user/login' + }), + loginSuccess(result){ + loginSuccess(result) + delete result.userInfo.token + this.setUserInfo(result.userInfo) + } + } +} +export default mixin \ No newline at end of file diff --git a/pages/ucenter/login-page/common/loginSuccess.js b/pages/ucenter/login-page/common/loginSuccess.js new file mode 100644 index 0000000..db1540f --- /dev/null +++ b/pages/ucenter/login-page/common/loginSuccess.js @@ -0,0 +1,20 @@ +export default function(result){ + uni.showToast({ + title: '登录成功', + icon: 'none' + }); + console.log('登录成功',result); + + var delta = 0//判断需要返回几层 + let pages = getCurrentPages(); + // console.log(pages); + pages.forEach((page,index)=>{ + // console.log(pages[pages.length-index-1].route.split('/')[2]); + pages[pages.length-index-1].route.split('/') + if(pages[pages.length-index-1].route.split('/')[2] == 'login-page'){ + delta ++ + } + }) + // console.log('判断需要返回几层',delta); + uni.navigateBack({delta}) +} diff --git a/pages/ucenter/login-page/index/index.vue b/pages/ucenter/login-page/index/index.vue new file mode 100644 index 0000000..f30fdfa --- /dev/null +++ b/pages/ucenter/login-page/index/index.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/pages/ucenter/login-page/phone-code/phone-code.vue b/pages/ucenter/login-page/phone-code/phone-code.vue new file mode 100644 index 0000000..8eb43be --- /dev/null +++ b/pages/ucenter/login-page/phone-code/phone-code.vue @@ -0,0 +1,72 @@ + + + diff --git a/pages/ucenter/login-page/pwd-login/pwd-login.vue b/pages/ucenter/login-page/pwd-login/pwd-login.vue new file mode 100644 index 0000000..4610d99 --- /dev/null +++ b/pages/ucenter/login-page/pwd-login/pwd-login.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/pages/ucenter/login-page/pwd-retrieve/pwd-retrieve.vue b/pages/ucenter/login-page/pwd-retrieve/pwd-retrieve.vue new file mode 100644 index 0000000..c28ae7d --- /dev/null +++ b/pages/ucenter/login-page/pwd-retrieve/pwd-retrieve.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/pages/ucenter/login-page/register/register.vue b/pages/ucenter/login-page/register/register.vue new file mode 100644 index 0000000..81f71d4 --- /dev/null +++ b/pages/ucenter/login-page/register/register.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/pages/ucenter/login-page/register/validator.js b/pages/ucenter/login-page/register/validator.js new file mode 100644 index 0000000..d5b4c27 --- /dev/null +++ b/pages/ucenter/login-page/register/validator.js @@ -0,0 +1,61 @@ +export default { + "username": { + "rules": [{ + required: true, + errorMessage: '请输入用户名', + }, + { + minLength: 3, + maxLength: 32, + errorMessage: '用户名长度在 {minLength} 到 {maxLength} 个字符', + }, + { + validateFunction:function(rule,value,data,callback){ + console.log(value); + if(/^1\d{10}$/.test(value) || /^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/.test(value)){ + callback('用户名不能是:手机号或邮箱') + }; + return true + } + } + ], + "label": "用户名" + }, + "password":{ + "rules": [{ + required: true, + errorMessage: '请输入密码', + }, + { + minLength: 6, + maxLength: 20, + errorMessage: '密码长度在 {minLength} 到 {maxLength} 个字符', + } + ], + "label": "密码" + }, + "pwd2":{ + "rules": [{ + + required: true, + errorMessage: '再次输入密码', + + }, + { + minLength: 6, + maxLength: 20, + errorMessage: '密码长度在 {minLength} 到 {maxLength} 个字符', + }, + { + validateFunction:function(rule,value,data,callback){ + console.log(value); + if(value!=data.password){ + callback('两次输入密码不一致') + }; + return true + } + } + ], + "label": "确认密码" + } +} \ No newline at end of file diff --git a/pages/ucenter/read-news-log/read-news-log.vue b/pages/ucenter/read-news-log/read-news-log.vue new file mode 100644 index 0000000..137f58a --- /dev/null +++ b/pages/ucenter/read-news-log/read-news-log.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/pages/ucenter/settings/dc-push/push.js b/pages/ucenter/settings/dc-push/push.js new file mode 100644 index 0000000..9ad20d4 --- /dev/null +++ b/pages/ucenter/settings/dc-push/push.js @@ -0,0 +1,118 @@ +/** + * 判断Push是否开启 + */ +function isTurnedOnPush(){ + var isOn = undefined; + try{ + if('iOS' == plus.os.name){ + var types = 0; + var app = plus.ios.invoke('UIApplication', 'sharedApplication'); + var settings = plus.ios.invoke(app, 'currentUserNotificationSettings'); + if(settings){ + types = settings.plusGetAttribute('types'); + plus.ios.deleteObject(settings); + }else{ + types = plus.ios.invoke(app, 'enabledRemoteNotificationTypes'); + } + plus.ios.deleteObject(app); + isOn = (0!=types); + }else{ + var main = plus.android.runtimeMainActivity(); + var manager = plus.android.invoke('com.igexin.sdk.PushManager', 'getInstance'); + isOn = plus.android.invoke(manager, 'isPushTurnedOn', main); + } + }catch(e){ + console.error('exception in isTurnedOnPush@dc-push!!'); + } + return isOn; +} + +/** + * 打开Push + * Android平台 - 打开个推(UniPush)的推送通道 + * iOS平台 - 如果开启通知功能,则打开应用的设置页面引导用户开启通知 + */ +function turnOnPush(){ + try{ + if('iOS' == plus.os.name){ + // 如果设置中没有开启通知,则打开应用的设置界面 + if(!isTurnedOnPush()){ + settingInIos(); + } + }else{ + var main = plus.android.runtimeMainActivity(); + var manager = plus.android.invoke('com.igexin.sdk.PushManager', 'getInstance'); + plus.android.invoke(manager, 'turnOnPush', main); + } + }catch(e){ + console.error('exception in turnOnPush@dc-push!!'); + } +} + +/** + * 关闭Push + * Android平台 - 关闭个推(UniPush)的推送通道 + * iOS平台 - 不做任何操作 + */ +function trunOffPush(){ + try{ + if('iOS' == plus.os.name){ + // 这里不做任何操作(不引导用户关闭应用的推送能力),应该通知业务服务器不向此用户下发推送消息 + }else{ + var main = plus.android.runtimeMainActivity(); + var manager = plus.android.invoke('com.igexin.sdk.PushManager', 'getInstance'); + plus.android.invoke(manager, 'turnOffPush', main); + } + }catch(e){ + console.error('exception in trunOffPush@dc-push!!'); + } +} + +/** + * iOS平台打开应用设置界面 + */ +function settingInIos(){ + try{ + if('iOS' == plus.os.name){ + var app = plus.ios.invoke('UIApplication', 'sharedApplication'); + var setting = plus.ios.invoke('NSURL', 'URLWithString:', 'app-settings:'); + plus.ios.invoke(app, 'openURL:', setting); + plus.ios.deleteObject(setting); + plus.ios.deleteObject(app); + } + }catch(e){ + console.error('exception in settingInIos@dc-push!!'); + } +} +/** + * android打开应用设置页面 + */ +function settingInAndroid(){ + if (uni.getSystemInfoSync().platform == "android") { + var main = plus.android.runtimeMainActivity(); + var Intent = plus.android.importClass('android.content.Intent'); + var Settings = plus.android.importClass('android.provider.Settings'); + var intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + // 安卓跳转设置页面详细查看(https://ask.dcloud.net.cn/question/14732) + main.startActivity(intent); + } +} +/** + * 打开应用设置界面 + */ +function setting(){ + if (uni.getSystemInfoSync().platform == "ios") { + settingInIos(); + } + if (uni.getSystemInfoSync().platform == "android") { + settingInAndroid(); + } +} + +export default { + isOn: isTurnedOnPush, + iosSetting: settingInIos, + on: turnOnPush, + off: trunOffPush, + setting:setting +} diff --git a/pages/ucenter/settings/deactivate/deactivate.vue b/pages/ucenter/settings/deactivate/deactivate.vue new file mode 100644 index 0000000..58fa9c7 --- /dev/null +++ b/pages/ucenter/settings/deactivate/deactivate.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/pages/ucenter/settings/settings.vue b/pages/ucenter/settings/settings.vue new file mode 100644 index 0000000..68ad813 --- /dev/null +++ b/pages/ucenter/settings/settings.vue @@ -0,0 +1,344 @@ + + + + + \ No newline at end of file diff --git a/pages/ucenter/ucenter.vue b/pages/ucenter/ucenter.vue new file mode 100644 index 0000000..12e1610 --- /dev/null +++ b/pages/ucenter/ucenter.vue @@ -0,0 +1,449 @@ + + + + + \ No newline at end of file diff --git a/pages/ucenter/userinfo/bind-mobile/bind-mobile.vue b/pages/ucenter/userinfo/bind-mobile/bind-mobile.vue new file mode 100644 index 0000000..29cb05d --- /dev/null +++ b/pages/ucenter/userinfo/bind-mobile/bind-mobile.vue @@ -0,0 +1,115 @@ + + + + diff --git a/pages/ucenter/userinfo/cropImage.vue b/pages/ucenter/userinfo/cropImage.vue new file mode 100644 index 0000000..dbec54d --- /dev/null +++ b/pages/ucenter/userinfo/cropImage.vue @@ -0,0 +1,38 @@ + + + + \ No newline at end of file diff --git a/pages/ucenter/userinfo/limeClipper/README.md b/pages/ucenter/userinfo/limeClipper/README.md new file mode 100644 index 0000000..9219f81 --- /dev/null +++ b/pages/ucenter/userinfo/limeClipper/README.md @@ -0,0 +1,227 @@ +> 插件来源:[https://ext.dcloud.net.cn/plugin?id=3594](https://ext.dcloud.net.cn/plugin?id=3594) +##### 以下是作者写的插件介绍: + +# Clipper 图片裁剪 +> uniapp 图片裁剪,可用于图片头像等裁剪处理 +> [查看更多](http://liangei.gitee.io/limeui/#/clipper)
+> Q群:458377637 + + +## 平台兼容 + +| H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 头条小程序 | QQ 小程序 | App | +| --- | ---------- | ------------ | ---------- | ---------- | --------- | --- | +| √ | √ | √ | 未测 | √ | √ | √ | + + +## 代码演示 +### 基本用法 +`@success` 事件点击 👉 **确定** 后会返回生成的图片信息,包含 `url`、`width`、`height` + +```html + + + +``` + +```js +// 非uni_modules引入 +import lClipper from '@/components/lime-clipper/' +// uni_modules引入 +import lClipper from '@/uni_modules/lime-clipper/components/lime-clipper/' +export default { + components: {lClipper}, + data() { + return { + show: false, + url: '', + } + } +} +``` + + +### 传入图片 +`image-url`可传入**相对路径**、**临时路径**、**本地路径**、**网络图片**
+ +* **当为网络地址时** +* H5:👉 需要解决跨域问题。
+* 小程序:👉 需要配置 downloadFile 域名
+ + +```html + + + +``` + +```js +export default { + components: {lClipper}, + data() { + return { + imageUrl: 'https://img12.360buyimg.com/pop/s1180x940_jfs/t1/97205/26/1142/87801/5dbac55aEf795d962/48a4d7a63ff80b8b.jpg', + show: false, + url: '', + } + } +} +``` + + +### 确定按钮颜色 +样式变量名:`--l-clipper-confirm-color` +可放到全局样式的 `page` 里或节点的 `style` +```html + +``` +```css +// css 中为组件设置 CSS 变量 +.clipper { + --l-clipper-confirm-color: linear-gradient(to right, #ff6034, #ee0a24) +} +// 全局 +page { + --l-clipper-confirm-color: linear-gradient(to right, #ff6034, #ee0a24) +} +``` + + +### 使用插槽 +共五个插槽 `cancel` 取消按钮、 `photo` 选择图片按钮、 `rotate` 旋转按钮、 `confirm` 确定按钮和默认插槽。 + +```html + + + + 取消 + 选择图片 + 旋转 + 确定 + + + 显示取消按钮 + + + 显示选择图片按钮 + + + 显示旋转按钮 + + + 显示确定按钮 + + + 锁定裁剪框宽度 + + + 锁定裁剪框高度 + + + 锁定裁剪框比例 + + + 限制移动范围 + + + 禁止缩放 + + + 禁止旋转 + + + + + +``` + +```js +export default { + components: {lClipper}, + data() { + return { + show: false, + url: '', + isLockWidth: false, + isLockHeight: false, + isLockRatio: true, + isLimitMove: false, + isDisableScale: false, + isDisableRotate: false, + isShowCancelBtn: true, + isShowPhotoBtn: true, + isShowRotateBtn: true, + isShowConfirmBtn: true + } + } +} +``` + + +## API + +### Props + +| 参数 | 说明 | 类型 | 默认值 | +| ------------- | ------------ | ---------------- | ------------ | +| image-url | 图片路径 | string | | +| quality | 图片的质量,取值范围为 [0, 1],不在范围内时当作1处理 | number | `1` | +| source | `{album: '从相册中选择'}`key为图片来源类型,value为选项说明 | Object | | +| width | 裁剪框宽度,单位为 `rpx` | number | `400` | +| height | 裁剪框高度 | number | `400` | +| min-width | 裁剪框最小宽度 | number | `200` | +| min-height |裁剪框最小高度 | number | `200` | +| max-width | 裁剪框最大宽度 | number | `600` | +| max-height | 裁剪框最大宽度 | number | `600` | +| min-ratio | 图片最小缩放比 | number | `0.5` | +| max-ratio | 图片最大缩放比 | number | `2` | +| rotate-angle | 旋转按钮每次旋转的角度 | number | `90` | +| scale-ratio | 生成图片相对于裁剪框的比例, **比例越高生成图片越清晰** | number | `1` | +| is-lock-width | 是否锁定裁剪框宽度 | boolean | `false` | +| is-lock-height | 是否锁定裁剪框高度上 | boolean | `false` | +| is-lock-ratio | 是否锁定裁剪框比例 | boolean | `true` | +| is-disable-scale | 是否禁止缩放 | boolean | `false` | +| is-disable-rotate | 是否禁止旋转 | boolean | `false` | +| is-limit-move | 是否限制移动范围 | boolean | `false` | +| is-show-photo-btn | 是否显示选择图片按钮 | boolean | `true` | +| is-show-rotate-btn | 是否显示转按钮 | boolean | `true` | +| is-show-confirm-btn | 是否显示确定按钮 | boolean | `true` | +| is-show-cancel-btn | 是否显示关闭按钮 | boolean | `true` | + + + +### 事件 Events + +| 事件名 | 说明 | 回调 | +| ------- | ------------ | -------------- | +| success | 生成图片成功 | {`width`, `height`, `url`} | +| fail | 生成图片失败 | `error` | +| cancel | 关闭 | `false` | +| ready | 图片加载完成 | {`width`, `height`, `path`, `orientation`, `type`} | +| change | 图片大小改变时触发 | {`width`, `height`} | +| rotate | 图片旋转时触发 | `angle` | + +## 常见问题 +> 1、H5端使用网络图片需要解决跨域问题。
+> 2、小程序使用网络图片需要去公众平台增加下载白名单!二级域名也需要配!
+> 3、H5端生成图片是base64,有时显示只有一半可以使用原生标签``
+> 4、IOS APP 请勿使用HBX2.9.3.20201014的版本!这个版本无法生成图片。
+> 5、APP端无成功反馈、也无失败反馈时,请更新基座和HBX。
+ + +## 打赏 +如果你觉得本插件,解决了你的问题,赠人玫瑰,手留余香。
+![输入图片说明](https://images.gitee.com/uploads/images/2020/1122/222521_bb543f96_518581.jpeg "微信图片编辑_20201122220352.jpg") \ No newline at end of file diff --git a/pages/ucenter/userinfo/limeClipper/images/photo.svg b/pages/ucenter/userinfo/limeClipper/images/photo.svg new file mode 100644 index 0000000..7b4b590 --- /dev/null +++ b/pages/ucenter/userinfo/limeClipper/images/photo.svg @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/pages/ucenter/userinfo/limeClipper/images/rotate.svg b/pages/ucenter/userinfo/limeClipper/images/rotate.svg new file mode 100644 index 0000000..0143706 --- /dev/null +++ b/pages/ucenter/userinfo/limeClipper/images/rotate.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/pages/ucenter/userinfo/limeClipper/index.css b/pages/ucenter/userinfo/limeClipper/index.css new file mode 100644 index 0000000..ce542bf --- /dev/null +++ b/pages/ucenter/userinfo/limeClipper/index.css @@ -0,0 +1,160 @@ +.flex-auto { + flex: auto; +} +.bg-transparent { + background-color: rgba(0,0,0,0.9); + transition-duration: 0.35s; +} +.l-clipper { + width: 100vw; + height: calc(100vh - var(--window-top)); + background-color: rgba(0,0,0,0.9); + position: fixed; + top: var(--window-top); + left: 0; + z-index: 1; +} +.l-clipper-mask { + position: relative; + z-index: 2; + pointer-events: none; +} +.l-clipper__content { + pointer-events: none; + position: absolute; + border: 1rpx solid rgba(255,255,255,0.3); + box-sizing: border-box; + box-shadow: rgba(0,0,0,0.5) 0 0 0 80vh; + background: transparent; +} +.l-clipper__content::before, +.l-clipper__content::after { + content: ''; + position: absolute; + border: 1rpx dashed rgba(255,255,255,0.3); +} +.l-clipper__content::before { + width: 100%; + top: 33.33%; + height: 33.33%; + border-left: none; + border-right: none; +} +.l-clipper__content::after { + width: 33.33%; + left: 33.33%; + height: 100%; + border-top: none; + border-bottom: none; +} +.l-clipper__edge { + position: absolute; + width: 34rpx; + height: 34rpx; + border: 6rpx solid #fff; + pointer-events: auto; +} +.l-clipper__edge::before { + content: ''; + position: absolute; + width: 40rpx; + height: 40rpx; + background-color: transparent; +} +.l-clipper__edge:nth-child(1) { + left: -6rpx; + top: -6rpx; + border-bottom-width: 0 !important; + border-right-width: 0 !important; +} +.l-clipper__edge:nth-child(1):before { + top: -50%; + left: -50%; +} +.l-clipper__edge:nth-child(2) { + right: -6rpx; + top: -6rpx; + border-bottom-width: 0 !important; + border-left-width: 0 !important; +} +.l-clipper__edge:nth-child(2):before { + top: -50%; + left: 50%; +} +.l-clipper__edge:nth-child(3) { + left: -6rpx; + bottom: -6rpx; + border-top-width: 0 !important; + border-right-width: 0 !important; +} +.l-clipper__edge:nth-child(3):before { + bottom: -50%; + left: -50%; +} +.l-clipper__edge:nth-child(4) { + right: -6rpx; + bottom: -6rpx; + border-top-width: 0 !important; + border-left-width: 0 !important; +} +.l-clipper__edge:nth-child(4):before { + bottom: -50%; + left: 50%; +} +.l-clipper-image { + width: 100%; + border-style: none; + position: absolute; + top: 0; + left: 0; + z-index: 1; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transform-origin: center; +} +.l-clipper-canvas { + position: fixed; + z-index: 10; + left: -200vw; + top: -200vw; + pointer-events: none; +} +.l-clipper-tools { + position: fixed; + left: 0; + bottom: 10px; + width: 100%; + z-index: 99; + color: #fff; +} +.l-clipper-tools__btns { + font-weight: bold; + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 20rpx 40rpx; + box-sizing: border-box; +} +.l-clipper-tools__btns .cancel { + width: 112rpx; + height: 60rpx; + text-align: center; + line-height: 60rpx; +} +.l-clipper-tools__btns .confirm { + width: 112rpx; + height: 60rpx; + line-height: 60rpx; + background-color: #07c160; + border-radius: 6rpx; + text-align: center; +} +.l-clipper-tools__btns image { + display: block; + width: 60rpx; + height: 60rpx; +} +.l-clipper-tools__btns { + flex-direction: row; +} diff --git a/pages/ucenter/userinfo/limeClipper/limeClipper.vue b/pages/ucenter/userinfo/limeClipper/limeClipper.vue new file mode 100644 index 0000000..076354b --- /dev/null +++ b/pages/ucenter/userinfo/limeClipper/limeClipper.vue @@ -0,0 +1,816 @@ + + + + + \ No newline at end of file diff --git a/pages/ucenter/userinfo/limeClipper/utils.js b/pages/ucenter/userinfo/limeClipper/utils.js new file mode 100644 index 0000000..980c439 --- /dev/null +++ b/pages/ucenter/userinfo/limeClipper/utils.js @@ -0,0 +1,244 @@ +/** + * 判断手指触摸位置 + */ +export function determineDirection(clipX, clipY, clipWidth, clipHeight, currentX, currentY) { + /* + * (右下>>1 右上>>2 左上>>3 左下>>4) + */ + let corner; + /** + * 思路:(利用直角坐标系) + * 1.找出裁剪框中心点 + * 2.如点击坐标在上方点与左方点区域内,则点击为左上角 + * 3.如点击坐标在下方点与右方点区域内,则点击为右下角 + * 4.其他角同理 + */ + const mainPoint = [clipX + clipWidth / 2, clipY + clipHeight / 2]; // 中心点 + const currentPoint = [currentX, currentY]; // 触摸点 + + if (currentPoint[0] <= mainPoint[0] && currentPoint[1] <= mainPoint[1]) { + corner = 3; // 左上 + } else if (currentPoint[0] >= mainPoint[0] && currentPoint[1] <= mainPoint[1]) { + corner = 2; // 右上 + } else if (currentPoint[0] <= mainPoint[0] && currentPoint[1] >= mainPoint[1]) { + corner = 4; // 左下 + } else if (currentPoint[0] >= mainPoint[0] && currentPoint[1] >= mainPoint[1]) { + corner = 1; // 右下 + } + + return corner; +} + +/** + * 图片边缘检测检测时,计算图片偏移量 + */ +export function calcImageOffset(data, scale) { + let left = data.imageLeft; + let top = data.imageTop; + scale = scale || data.scale; + + let imageWidth = data.imageWidth; + let imageHeight = data.imageHeight; + if ((data.angle / 90) % 2) { + imageWidth = data.imageHeight; + imageHeight = data.imageWidth; + } + const { + clipX, + clipWidth, + clipY, + clipHeight + } = data; + + // 当前图片宽度/高度 + const currentImageSize = (size) => (size * scale) / 2; + const currentImageWidth = currentImageSize(imageWidth); + const currentImageHeight = currentImageSize(imageHeight); + + left = clipX + currentImageWidth >= left ? left : clipX + currentImageWidth; + left = clipX + clipWidth - currentImageWidth <= left ? left : clipX + clipWidth - currentImageWidth; + top = clipY + currentImageHeight >= top ? top : clipY + currentImageHeight; + top = clipY + clipHeight - currentImageHeight <= top ? top : clipY + clipHeight - currentImageHeight; + return { + left, + top, + scale + }; +} + +/** + * 图片边缘检测时,计算图片缩放比例 + */ +export function calcImageScale(data, scale) { + scale = scale || data.scale; + let { + imageWidth, + imageHeight, + clipWidth, + clipHeight, + angle + } = data + if ((angle / 90) % 2) { + imageWidth = imageHeight; + imageHeight = imageWidth; + } + if (imageWidth * scale < clipWidth) { + scale = clipWidth / imageWidth; + } + if (imageHeight * scale < clipHeight) { + scale = Math.max(scale, clipHeight / imageHeight); + } + return scale; +} + +/** + * 计算图片尺寸 + */ +export function calcImageSize(width, height, data) { + let imageWidth = width, + imageHeight = height; + let { + clipWidth, + clipHeight, + sysinfo, + width: originWidth, + height: originHeight + } = data + if (imageWidth && imageHeight) { + if (imageWidth / imageHeight > (clipWidth || originWidth) / (clipWidth || originHeight)) { + imageHeight = clipHeight || originHeight; + imageWidth = (width / height) * imageHeight; + } else { + imageWidth = clipWidth || originWidth; + imageHeight = (height / width) * imageWidth; + } + } else { + let sys = sysinfo || uni.getSystemInfoSync(); + imageWidth = sys.windowWidth; + imageHeight = 0; + } + return { + imageWidth, + imageHeight + }; +} + +/** + * 勾股定理求斜边 + */ +export function calcPythagoreanTheorem(width, height) { + return Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); +} + +/** + * 拖动裁剪框时计算 + */ +export function clipTouchMoveOfCalculate(data, event) { + const clientX = event.touches[0].clientX; + const clientY = event.touches[0].clientY; + + let { + clipWidth, + clipHeight, + clipY: oldClipY, + clipX: oldClipX, + clipStart, + isLockRatio, + maxWidth, + minWidth, + maxHeight, + minHeight + } = data; + maxWidth = maxWidth / 2; + minWidth = minWidth / 2; + minHeight = minHeight / 2; + maxHeight = maxHeight / 2; + + let width = clipWidth, + height = clipHeight, + clipY = oldClipY, + clipX = oldClipX, + // 获取裁剪框实际宽度/高度 + // 如果大于最大值则使用最大值 + // 如果小于最小值则使用最小值 + sizecorrect = () => { + width = width <= maxWidth ? (width >= minWidth ? width : minWidth) : maxWidth; + height = height <= maxHeight ? (height >= minHeight ? height : minHeight) : maxHeight; + }, + sizeinspect = () => { + sizecorrect(); + if ((width > maxWidth || width < minWidth || height > maxHeight || height < minHeight) && isLockRatio) { + return false; + } else { + return true; + } + }; + //if (clipStart.corner) { + height = clipStart.height + (clipStart.corner > 1 && clipStart.corner < 4 ? 1 : -1) * (clipStart.y - clientY); + //} + switch (clipStart.corner) { + case 1: + width = clipStart.width - clipStart.x + clientX; + if (isLockRatio) { + height = width / (clipWidth / clipHeight); + } + if (!sizeinspect()) return; + break; + case 2: + width = clipStart.width - clipStart.x + clientX; + if (isLockRatio) { + height = width / (clipWidth / clipHeight); + } + if (!sizeinspect()) { + return; + } else { + clipY = clipStart.clipY - (height - clipStart.height); + } + + break; + case 3: + width = clipStart.width + clipStart.x - clientX; + if (isLockRatio) { + height = width / (clipWidth / clipHeight); + } + if (!sizeinspect()) { + return; + } else { + clipY = clipStart.clipY - (height - clipStart.height); + clipX = clipStart.clipX - (width - clipStart.width); + } + + break; + case 4: + width = clipStart.width + clipStart.x - clientX; + if (isLockRatio) { + height = width / (clipWidth / clipHeight); + } + if (!sizeinspect()) { + return; + } else { + clipX = clipStart.clipX - (width - clipStart.width); + } + break; + default: + break; + } + return { + width, + height, + clipX, + clipY + }; +} + +/** + * 单指拖动图片计算偏移 + */ +export function imageTouchMoveOfCalcOffset(data, clientXForLeft, clientYForLeft) { + let left = clientXForLeft - data.touchRelative[0].x, + top = clientYForLeft - data.touchRelative[0].y; + return { + left, + top + }; +} diff --git a/pages/ucenter/userinfo/userinfo.vue b/pages/ucenter/userinfo/userinfo.vue new file mode 100644 index 0000000..7e3f423 --- /dev/null +++ b/pages/ucenter/userinfo/userinfo.vue @@ -0,0 +1,284 @@ + + + diff --git a/pages/uni-agree/uni-agree.nvue b/pages/uni-agree/uni-agree.nvue new file mode 100644 index 0000000..5befcea --- /dev/null +++ b/pages/uni-agree/uni-agree.nvue @@ -0,0 +1,139 @@ + + + + + \ No newline at end of file diff --git a/pages/uni-agree/utils/uni-agree.js b/pages/uni-agree/utils/uni-agree.js new file mode 100644 index 0000000..3d1340c --- /dev/null +++ b/pages/uni-agree/utils/uni-agree.js @@ -0,0 +1,11 @@ +export default function(){ + console.log(uni.getSystemInfoSync().platform) + let userprotocol = uni.getStorageSync('userprotocol'); + console.log('userprotocol',userprotocol); + if(!userprotocol){ + uni.navigateTo({ + url:'/pages/uni-agree/uni-agree', + animationType:"none" + }) + } +} \ No newline at end of file diff --git a/static/app-plus/sharemenu/copyurl.png b/static/app-plus/sharemenu/copyurl.png new file mode 100644 index 0000000000000000000000000000000000000000..270e6aee210a59d179d67ac3e44d169d030a8721 GIT binary patch literal 920 zcmeAS@N?(olHy`uVBq!ia0vp^c_7Te3?x6cUOUOaz^E4B6XNQ6=HkF2pnE8Bz@}Gk zp8xpy_Sf%|A3k0F{N?_)?+4#~2)S_e>gTVwzJ6Qr{MG)q?;m~t@#o*aWzS!B-+xqg z^X}CzU%k&=>U;3`_rHJTx9}APPW}?eP%ZVY_k7_$;nO?9fAqh7qqY5p*hk}xKrdkEKlF5Q49U3n_Eu!LW1z&b zkFlCsT1yx;4||p_^0D+XIlp*IW=j2k{fW<dy$>XaKkDs^#R=bv3@9`&Y}7 zY1gxM&RjELno;aoRvV|Jc?Zh#ygc$=X+QsR;+f4k|2!YTOTowPS?-Tz*!26p#p(u= zPp4W%c$T-s?MP%i_4UlnI~wP5R~N)Fo{oNa#zJr1CLfXGuFhu^8+eU)PiE@$K5@IL zGHG^-m`ZvglT@MXBQDjr+b7rR^j%+D!TYr0AJ?hEy5P2=KZ@*6e>}CF@_2T6K%ae_ zgXKK?_M$oSr!JJSKezRCuFSr&D_qCW z%YA)<@!!Ya@-fYR-xtJOzCO|7(LbB=%~wUVMPF!K=FDTL){AkKcRsj0?t`Lq)w1{L zYHv6gEjJn@v#ZqpsO)*=RJhL1XZC@^x>w&jXYBhPIZNuysp~p>=gtyY#6J0j<%(5) zr)75QDJfs$z)*F9c^srsB%zyho<2Mi2u_+fvA6fJK#2uN9rgyf48(D5? nF}(AP-$~3&&7^(hvxojwds$A2CHpo46Apu?tDnm{r-UW|->C5L literal 0 HcmV?d00001 diff --git a/static/app-plus/sharemenu/more.png b/static/app-plus/sharemenu/more.png new file mode 100644 index 0000000000000000000000000000000000000000..bca03ce8015e5317bad3c5fd6cf7d566d6cacea6 GIT binary patch literal 1538 zcmd^;|3A|S9LFb#n{s%NzPQ$xOQDG5%eeAog-7z(i57949vk7Dh^9RJzRv@ z*svzVw2$vg`(m-#42uo>(kjDl=H}aNbNb;P_eb38@p`}BzrNqk$K!>DhX!r~?*fBB zpl!iHu&|At{B=wWH*(?kF%}54$qgP7etP}?tqmF*0CZZdOePx}9hFL@R4P@c*DozC zG3fN+;o;VnmWuN7T0Fj|r$;0bVXLYz7>r7#Vlo(fKA(Wc*VWcGH8$4Za2@UKGO6^F zT&`BDM@B~anas-aa$WHMJ)R>sE0x;i`i`}@1Q zx>zjM>gwwJ+}x5zBNmI>0f0`YOC*xzm6ZjRN~hCtIGp9>Wg?MSSy7=>Dy0$$iA4H( zO=TsWM&t2#0)gOlbMp{~BNPhpH8or=x2~@4gFw(mCNC~7ZoFAvS2r{?gvDSQNF)FN zUJ{9zii-C3cK-12^z^iFVnU%%)Kpgk6bg+-n^7nxCnvR9?cCg)czRl|*R$E|w^ZuH z`1smWSpEh$#LckK^O?PpMXwzhazOc!)rgLD|CVB&rvG9$HUHRAhVn?zY)dXyo81r_ zT>i_`Z3%j7yYPK z0D%ncf?k!hUH`fyymYrq2nEzx1=tQ`_f zpqlskc|p~gM?#K$lHIreP4L7A0?h3{Zq{sx96t1x@cpcd!OXLp2|!Y0X;GTYPI^UC zT3XGw0!rD%rPy?3;X+@M=n*`~D%Py3q9V_Z%q-5~mK!jspss-Gw>u#hyI0l^aj$uxY^R`V!`9wW7u+Hz>o0`w{ z52`Sl&!QRCz=EQ;M1j!!8tWq9N_a@BRp5U*9}|J+-V~AhGgyu2K)8_KiTW zFzx6jjarLH&4r<&2~~g$5`GA&YK=g-CS`_RbtZL$L%%s%!gBZbC@T=Ru_HA}nFmsY zBIZ5@Y0s6(CLNS^*OoVAV#|}+a7{bAdy@JJ)U87IIPZpZgG$OeX35B{R-Kk4m^INM zaGsB9{&l&786e**f*U5=Z1MoO?XS*Ek3(bD>Hg&DZ$R z@nqsnv#n0S@6m)fKW^ChXk4df9yi834xe&iV%;?4>60vF1kI0?6BB>+B`hPps9L^- zXjBsaZ1n~Y!aZmCZmQK@?2?H5@l9b-#_g8WTjGnWjBNj^vUEhY9$RH`=iZrS0u{cU cV5@_8&5Cj(m^-#?loAwtIuzFE|MSCt04w9j5C8xG literal 0 HcmV?d00001 diff --git a/static/app-plus/sharemenu/mp_weixin.png b/static/app-plus/sharemenu/mp_weixin.png new file mode 100644 index 0000000000000000000000000000000000000000..5ff5908238fca590df862a3475a627d2692c6cb6 GIT binary patch literal 8250 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 &vJ62_vNZ)-@COpwOh((g- zdfPEYJ)0Q^D9KfgtL{$fIzCH!7hRi@_ONf;uiBNDlqcpe9>*)u&2@Y&bM7v!VX)$N z1xkg(=7ES6!$A<*z3{ z?W?$q`8-=v1hj`(Lp=R6pl%6Q54yY97CY`a{bkmFX!i9%1uY=IQ5iy_(fJjK~lwz<=@siaNmKqrEWx+tsUeS|1D=3v~p6h!&>?X~)L(R>7L zzdn0Xao_7)ciR^NfPGBYMc%p?1l`&g9p%2uN+<$OsSK_nB-WZ4C|buAy(|DfOS_b9 zr0*j%z)qv|kk*CmFj%Rm?Gq`8cUINThQY{$?aKQhHI!NPWH*s5| z_q;Wb4y+8Bn~fIg1LyW6o5ZN%+ml`WyPEZktD=(S+C8>mj-Ay(kR zxyp-~Y?JCx!ZvS62!Bfytro9n+@p`DEk3uovTKO1MBEP}X?kY0wew?Oa_z=n!wTsb zeO?j=wdq41$(7ag7j)M0c2)k5rbjU3s%fIaW}mT9{6LQd^pS3Pp=401TIN^#UqHk| zIwWJ8KzhIz@_NoVhx=rr;DoXqp8Jqae-hs~%KMHs83N3c4;U&bs~J{e$3WPyBqXf! z`g^@BP$h!Cr3@j!tgU82VSPRhyTgPf2&PdJeM^b?0Yn2b4SmO+h0o_J2s56jWVah||r@!Wk@0_l^n zCM#HwYWG|zr^s6q)2APPKkr>)G6Ol2aHQfO3BTXtx6V8B*)|?>iln-^aJV%~k=Z)VGDrCF=aIMlL7FC z0c=F(6Ul|R&0)1exiZ8>A5k7)oIOr>Fo8kvToE22DGPdj55kbGI9r@Bp zuy?wb5ow%=EU}3MKig8mn@c2+-GUfO13T*o7e8I?)ubf16kuiuV&4usqJfEEAa_VX zGOHEQWd5K%e$P``g2)JM5n;C~1`b*yjL=az*a0c>3Zek8WZuWr;m>}f3UHIQ*C z**^HSf;8fCe4Wj#UtBO!;}fF`T!!+(O`$a9G{t_#XGOiXP~Ho7+L+E!ony3eFs4o= zpP`YUf}wi|$P)F-)D2BkgUtiDUAG;jrx6Do?C_z(f{$@-%Wzu2TY1vLI~jhd1rG59 z#TD_CaRAdvt>;m-BpamcAFsg^`r)mlynHKMRCrlG;pH1}7=9NR-6xP^sBA%SguJYR z-r|+>VEAM8ew`1PsBZmOu8>^^6}JNkD!9^RiXmOVKwKKp1%{3{25Sz%Y0=>$kVO)z zh}4tZ50(}DwjJqlK7{597A|4}66SVc6|BIT48vCU$UHPqp%-M_RKL9?6ovoTD|^#L zDDU?ULV|HnJ~E+gcQ-1SkNu_@AC~+nIW1TXRB)>+u3LHL#_r3^Y*|V}a!+VKGsQ){ z@J#4{YW7+hE%N!g57PAu3*pd4_|T3oNZy$DH2Pdx36?&MOpdG*!j~U@)9}d<6aRd| zmQ*(r;KuRFr4{>+SRhFl-642$+!Db*85ez_)^>M_QBPwKG4$qnMjUm2p1?w{{Y?WJ zB`s3O^K%Tp_~J{#RKbs~-6dpIs@{Z(f$wO2UMY?`z3N%Nw8EOX2PN~-?tH;Q&^ki& zHwVJpbhOy`ILYSkf)kLR!l7;)A=IEuShn8|Bun!hA9Cz2Elsl-8$rcolniia&;rji z_!CbEL2Rd+S7);$k$3(MI0^FQQmYxqB2y3dcKpYrDV<9R literal 0 HcmV?d00001 diff --git a/static/app-plus/sharemenu/qq.png b/static/app-plus/sharemenu/qq.png new file mode 100644 index 0000000000000000000000000000000000000000..28e745655ba66f66917144ff8ef4f4eb39395a15 GIT binary patch literal 1605 zcmdT@`8(7J93I4?R5DbwijvK`71?z(t{h<;C5#*y#}BfQO3F;Qtz3y=J!dDu=aUK#m0NJ&Ge@w&1jo=1=t^?@rOn4hY zd$~wC8y?v}9u7kYivCyy?80bh9|R)k`&oEu6H%iAbS|<3p&#d9#wKFc2nO~-tS!X7 z1JtPnR`*aCL96EB)G>&;g*1Id%$q=+I$&iN1z_~?D0I0F@aP0h8o_t7uuB^lKLq{y z1uA60DWg!u9J~jk18Ybw1OB>;)-J&5;}ClXsau3ICZKE0pk@u=(he$D14HY`zPZJ3 zh<`VDwFwNOLmE^dZU{=BfV64=m1^L6D>%BbZy)`<1mEd_)Tw}B1JJdKyq<=6AW9j5 z#&}4>BCK5t-0A=$1|WP3*tUX%_d^9tICBEx>>x3NkaaT{)(`D}3v~e|c7dPf;iqE| zp&d*dhH#(3?h>c_`+ZyqwYPBqPNm8S*_{s=KTgni2ok=7>_+MNZivS~S{5_&lY9Xub}C0TP`c(ytF zbPJZ;y!Y-wFx@sG=PFyLt(2^uyjy|>Gk>#^g6BR;8|0aR$(zl2$Nc6parM+QMIA{V zfg<9%f`+PHKb%|K3yvydJ=XP!^I&zJO!O{bc+p-dE~rKDIC_FTmj)RZ+n?Nt3H4os zT@&%Q$@z19x4CT|XDfQM!KoE$c96>e$8q$A=B!fJ^EV&ee4DqO29?wg7Gl9pK+3-H0SmgBa-uz6RsvX;243eWloPT)Wme=XK|wVY7Se7Wno)_)e3b( z#Z#9RSTRk(#llp1_40<42g`hHQIU&|c)r%*=cE!5N_UXlEYHl7I!aL%P-h6sISI88 zB|@e>sdAcS+tR|>la`n3(l7)>!zB05vs(;LlY;0W#?R*z$(aN+z?q=^{b)hLQO?$Z z2cO2Z^Boy{HF^Gqf}?TX?M5_W*Mh18NuO6k#xid05kv%i)BEH^=}`bz(|*yfAY#md zMtrm|ns+vzoocgzx22JdN~GAU-Yyf#L9;2@7Bu~UJtf&A&#i;rhv^4d6`_|tC=8^! z#;(Mq^6TkgGZ?WoBsD@-agqBPk8gUd*2bKi<;r6AI>?0B7y8_ zd=UxStoJ+`cw|XQo4(UR`qDpKp!!hmhcW#(L~IKo2bNJZsG2VGuSOJ|#=P%| z4j>)`!qxN6?+twp?au@PS z-(h*G*LdX#(obp1-Q|^$HzLXDrzEqcUZ^|M<8RdImU{N(inzDz8EqK`xC}knA(_>8+pY43$IL@vH$=8 literal 0 HcmV?d00001 diff --git a/static/app-plus/sharemenu/wechatfriend.png b/static/app-plus/sharemenu/wechatfriend.png new file mode 100644 index 0000000000000000000000000000000000000000..e6af3472fe40c35b6a31e2ceb4a0d64a4f1db3db GIT binary patch literal 2024 zcmdUv`#;l-0>>wpdb&8B6wwVuPpao+MQ%@zyNHDelWUs9Tr*_lp(8!Iq9(Z%$6Z2U z%Uq(1T(ZsWyV-_~hV46Jw%N?aAdp?o7wtVa zJNm!gro5Sg`#>KAvW4yL>g8~TX#W4AP4fRmU0hpy{4*zLJgkNP(SF4BPgaH{*{*p3 zzq&39qoZ*AR_Y3ER1BDsE+D8OzLcOT3G>O%+=98+mE79z%Y9u-pKXU-YQc9aiWQc0 z`ahh?uB9&@`E4^&w(A%?Y3eS9nSN{XcFY9&IXnO2sJlDh`*OC(b<9h)E?eJNM^J;y zIn~wNx;n72k=JZFWS`2+I7_nnWBB5Evb7o6#(LO!li6y>fgzbBi-x}nda`Do=CJdy zvjt0QOHzeYBp2~yy!Q*u@^yLJV&_}1{)?c8w!$C^$&Y?!17hm1XjG&S$4w+LrL3Q; z?EBM?(peAClbCrKhqtz{Bp0Ny?sqSJO`N(D!HDfx?24aEzD5i0TOJq`lE%c8A<@XR zbcVJTyK+{7 z>`Le3OzXgXV-~GY9cAVg?a4zjrJ4B4UrN>DN=LAQ&Vh+VneF3(Z{&Q0iF*r9(eoyjMU zSZJtOruZH2*LAoImhYF&-#m20PCKBx8eIlF@pHO|c4$h1HBj-_CJGeTla^DdWi{v| z{NbP$b?s9F#dc@Gz-UC1x<UimZ6F9cFA+sVoG)E<1NQP?jpS_z+G1m+RG z__W}|)l_4l-k1v(DP8FA#RMd-_f)lFd>|dk#|SG+=lkmhx+3YKD&(ze+#Cm;PnGW1 z4qX`FHoWY(o5o=+Nc!;gO86+a`aV9Y$MAjCOB6_pqv3c|#V)gQD9QiW2mx!R(O0DP zD0b}*d+T!Dr7k4%JA6VqjkdfEU@4*J`OF8u$`Qw2x9g`INE``7MrD5R{$_tDEel{b z1Ks%!*AbHIs-|>J?7IiX4N8$r&2XX_!h3~x&daK>ohv?S3f3OEq3>h=Qt!uBU2$aT zbX!m^CAlk)F=N{O>ScOFr|nt)RK$4A_I!J#+iqoAJB(5EAn&Z+KQ(@9`K%+H$U3{5 zW0LtJb85t39+)@@>M{JeNAU=Z<(|Rv$~OCEqJD2obAyw~39HB3KpCZb z<@|Czt*XOJcqR*y YoKL7YX6RVB`D74h2bg`mZScLn0aHIo8~^|S literal 0 HcmV?d00001 diff --git a/static/app-plus/sharemenu/wechatmoments.png b/static/app-plus/sharemenu/wechatmoments.png new file mode 100644 index 0000000000000000000000000000000000000000..6445df06def179c84852804a4701a5b29efd6e9b GIT binary patch literal 1758 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 D?n@74 literal 0 HcmV?d00001 diff --git a/static/grid/c1.png b/static/grid/c1.png new file mode 100644 index 0000000000000000000000000000000000000000..9d38fdc45f54393919608143278902961ebfc03e GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0vp^DImv+@EMfolJl@2-tdeIgiE72V3$+1a&#<;nGqB*6-wd0Dfno2^$& z7L}LYx}4kkKI1~4j2xSM#i^{e>mxFDC0DcwoaERsZFdmo&xZb=|e; zVChPF@z}uo`_jyFigEMR=cKgdY*81V{;5gJlXF9MvNaZ^cipGtGKCwGwYFdLih#FiI|b%1`}h zcD?9wzL;(MG`RGl!=KdO=X$Ia3G&T>{3@pZ$E>nWT~vAlEaktaqI1klarbacw7TzPB-Tm@A?1# z=DKt1Tm@&jBni*W72Dq8$U1XQ@j1)qcYd?AM-~6vdS%zgB|W!|l9z8;VdkK15fZWZ z0_Xa#{=&Tl?+&}Myixw!c0&2Cz{(%uYaLfyIpx20a+b;i&8Hu}PrN-Tsf8gx{xaWd zSEda)?3G(j%z6CKKWpOGAAJoS5gg_&aw+l?Pq*dmh)8I56YfnYkc%|gRM+fjvg@w| z$E(g+E=n8A7O8TmM)@~hDG@eQ_vn5n5wpR0%`A=`87W~$m7>*=T~gqVW%D$JmT%Wy00-NUV`g= zPWq+lU_K79dVw=Ll(hEL%sa^y|HiF3Bqng#c7b&Xt+%gdOo& V%D;5ZBtB5oc)I$ztaD0e0stIlwo?EA literal 0 HcmV?d00001 diff --git a/static/grid/c3.png b/static/grid/c3.png new file mode 100644 index 0000000000000000000000000000000000000000..216202ad01c5d093a15c247cf791bf66759cf54d GIT binary patch literal 511 zcmeAS@N?(olHy`uVBq!ia0vp^DImqEwn3#C`6axcex~Gd{NX4zUH%{jrQQ%<>_`I#L<;VZ} zZHv8>O^d80dDUJWN}qZ5sw(5+2E$_#$vr=hznV9v_(!hA^s)ehHzYuO_$GPFaj>L^Z^8*-5pGM3JjCnSB#oUXR4yIck*z;L5euG4hrQnMaF=Mr^If6ezqfPO{rmaH zG`Recv6gZBZ^h$ro*MqgZ`L;0*JWSQ?4MdEEc#Ycl^2Ku*&||7ybYSKxgR(e zUi8LbW#8LW_O;o`+{cdv9h)e%;+1X1dv+zh{}-wmlY4-{_IaWH_ctMZlX+XXL2>Bm L>gTe~DWM4f<{`=m literal 0 HcmV?d00001 diff --git a/static/grid/c4.png b/static/grid/c4.png new file mode 100644 index 0000000000000000000000000000000000000000..fb8b4770c323cc87544a502c4efcd9792ec6047a GIT binary patch literal 476 zcmeAS@N?(olHy`uVBq!ia0vp^DIm!v{Zmph6H*@EOQ`c;Jj35m?s8tsB!ig_bOsad3NWyNKsO#%u4s6f}!WF<$1wue~HTU+T}QZ;Gq4 z+^p*LR=oRR<2>VYrDayznw7JEgn9(s4mmKdKH{5e^24HIn&xXKq`oqWXf4s;nPY6> zl5RSUO)+g!*Qq{!;a&O8LAO2%J{IadS>Y&oFLd#-`B8Zi;r0eCLdRUCfQpstnaz}|9;&C$edE%qT=zMqcbI`S>(i_Uwsp-Jh?$Zac<}T=h6;^ V(bfHGPg+0`|eiw2lA( literal 0 HcmV?d00001 diff --git a/static/grid/c5.png b/static/grid/c5.png new file mode 100644 index 0000000000000000000000000000000000000000..310bfb123ba811240a23883c589557cced22af97 GIT binary patch literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^DImMU#{OfDLKp{`iu-m;URIy-X0%w>4GPR z_aE3Db+En7Vpp-?y{8>c_3j;K1E(wImUzhRpIo-4)yng0f9$<4bN?NcJ+$)R>Cmd5 zPnT-Q*3a3qRzbM)VHC^C!+&Q!2$x98T^XQ1{a2U4m97Z?l}kQ1avTGN#j9nTb Uw;LB#rGg^I)78&qol`;+04O}Qp#T5? literal 0 HcmV?d00001 diff --git a/static/grid/c6.png b/static/grid/c6.png new file mode 100644 index 0000000000000000000000000000000000000000..c3c45d8814b2657a1c603b1a44c8207da134cb40 GIT binary patch literal 545 zcmeAS@N?(olHy`uVBq!ia0vp^DImqbxJT^6j8j=}L4 z`U~zp;jZGGq_3%#*Y{@jiL-*|KAoCX9M=6tMV3p`Xx_9LpK6YHtIv61@a%(@?tX{a zixmsYle>5975wq9?fBo`FZ<0Z_ST1U_$og>`Bwh@oq)-Q*IY8`n6H2IUEezHG>gnR z4m?JSR+TGA8lKK}%#OUITJo;LRm!DE@lpDwn1HB=DfxjiElxjmC-aCcb$n6qV?vwH z70D$Ac9y-Kb4tS6k2yc|sk#5)HGj$%vfgZBIOdhE(svqtDm`(*VzL-mu^Hy19AKPf2Xw<}^wvyk3;W5tc1>eFM@ s<<*_%cP|y0-yh@^lK=|O<{9 literal 0 HcmV?d00001 diff --git a/static/grid/c7.png b/static/grid/c7.png new file mode 100644 index 0000000000000000000000000000000000000000..a1e73908c282c8ce053a037a9b0d0ff2bcdb5f28 GIT binary patch literal 365 zcmeAS@N?(olHy`uVBq!ia0vp^DIm6ZD L`njxgN@xNA8ryiD literal 0 HcmV?d00001 diff --git a/static/grid/c8.png b/static/grid/c8.png new file mode 100644 index 0000000000000000000000000000000000000000..c32633c0b399d119fe940b19fb48ee0efd986ca1 GIT binary patch literal 587 zcmeAS@N?(olHy`uVBq!ia0vp^DImtDWKw)GSgG z<{?!7vyg^n3M^01LJZ}7srr_TW_zQ?t87k(~wwf+9)vT z%KQIoiZ6ORmsqxitIvdcU#@t4EXP6v-^(UI#Jk~jfg$H7CzX--mMU=ckjl- zO_N=n{=9#BNIpzt0(^6iu(G%&IbVv*qm>1hi!XSEd0U$OB}=(W(sDW)~2<6brG`eb|Eywk@UH2a9_S~2ou;@L{mE;Re4S!9(w0H>~IdYlxQPbf~&0B26{}evs0SCD6 b<@sM36JAtSn%f$@0;LB}S3j3^P6r-DwW=|@#WR(W@( zZdqJkeNu(zZcxH)-rk0j)>6#pwJ%5 z({0I4*ENL;Hg>s)Wo2yHMQWq+YVK10K1LEVCnUj62Kls6uex~DF( zmCs;?h^>{Qb=Fc@o?qGaU(P$0_Dg@wVQpV1_%AL~YBQ@%pW7wIWu-GZ7%RRBC{J)$ zYTez|@_3zqf=Wi}%JdkW31MA*>f4V5YcgIo@dcs(Z~1ke=Y_pcHP{M@c~4hAmvv4F FO#nHb*8Bhf literal 0 HcmV?d00001 diff --git a/static/grid/empty.png b/static/grid/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..c836ffa48680ae45bd5a125f2ca5cd66f5799774 GIT binary patch literal 5333 zcmeHL=|9wM-&WTZJr#1LxXX5h#6*iNQ)rPwcE&o0EQ64J8$z~hx5`Ym6xnAO+gQeu zt;tx^FjvR7J&*$^Jx!=spi{Ed~?|GcZ_c)I4nP_BipZD0=V_aNZ zygCoGOt`oX1%cn6k8*?0Q;_#dTwH=VI$F2Qf^+Cpj;R@ChPcL$yIFITbgblE{-3yG z#?fvVfqWhJoTA)^6FS9;ocEkIwufuh=tJ>gVaZR4iTodry)jScxh-)%?fm*L4P>Ol zx68d#aK`!SV0HxOr`Xn%ALYjGnBSlpYx2YB=;%8xE^Qry%|l#VDq=HyT-@g;4}HA# z{05hBf)5YZ9|8<62Z;_*t`k}fe>{si8pL(ws;k38LPC5S zZRH@4uc@Lp>>68lc0!n2TT4B!WU_K{$di*fza!dAOiW(q=3;;U4yTXR+~?sFF$xW3 zbBA|ykrOF0rFC`6Z+^wx`oP04R)WRqD=I2_w5qD9S;g~-n!w??CZ?uNt*pzDJ4xXh z)7qj%Ql-_^CMzv0JFOiijl%ri9uu&ldgF#ol)Zi#|EEY>Z0B6MJBCZmS>);I%R0I`I>zSa86*@sWK&#Z;8omFHXtUqnW3?$mp>l}QBil3a;@Z5l_V(NG zCV!pw@B4Wj9UXU&>pe2My1LbqE(JCWSy@^3a?Kz(Aj#&sL%J0S;U)AXxTwMp*!o&C77}@-C~2>YERLy7CV4F!ofkp4E5?&f&Jb(8sDiR z5a77sqhA->KxG)=^h^NweXw91okTuSs31%QP;+2lfD)KoZfkK$|0Q!>vtW>^%r$Uo6{%9oyl14lQQ&CYV;_R}BlNEqPLdWWk3n{pP=kZ&jdLbc=Tgzhw zdwUVoR^*LKZ=_<`Q5HPQRZc`R6O;U{tw1J|8B0of@xlxa$G&{|GNB0yg?8AbB`0G- z!Vw4rG?XhW>`TNp^F9)T-E^dCMfDK3`5 zVDj_tSXo)&mmr`!E8v6<()jrJ`H1bm;4&)WGp+Y5nK2WKqYnT(MTTrWChDgnCidE~ zxZlzSdU~e%`ub!RR^(xSPmh%aysE)gA@^p;HH@dD;~mgas1?>uxVx7ZN=jKp<&&q5 zO~BQ@fr0U*P#jdKiW_BNAD~k`a$lUr(a5`ezWLOD7HqJsNEqF~Kbi1+;&B>UW&Pvc zy?Zow{lS5OxajvqMHl967v^sDe*T=`_Q?BnS(zN2N>Ii0(F1Mp)!tLhrKP2^BV_U~ zphH(#G=HdM#e*D!Vr==ja=$9yk6{5J-rnApR@T;8FJ8PD#kdv@4-Y3)yWP%CfTjyS z-#wF_nOO?ZgFqm9;H1afBHN~rEV#`?Xv-9iqNAg8)%CJvfatdw(08oejP&&PfOo|3 zmGz8Hnp<6^|9oF2PJ-{x!|bgI@6J10YqP}A)$dV(^zp)3Vp?V6;Lo3rm6en(f^OxD zzYF%o2F>t9G|il%Ra;Ts)6*{_NYjO~9q6!eicz$tR>$45EG?&u;W31%l6JV`h<&~|+ zGE&mgdg+1X6%_`cwVDFyS=BW)0b9#f6Mw@@OdQqJedS>mys@#ds1>-HqPrF5Uuso9 z=jLL0+1!T@0itOA{ZK(6m8;fI>cdUV^J!X+CmG&2hbzKY zuft$4{QPE5a7s!_Y|%*&MYQ`As;WiKXA3Z1n%Ub|(kJTU?Ra8tBX{P|q3q4lB^s>& z|5numxXWA3NHG}9*jS*>*;BayfraF|VJgU&t-OOX_3v$N_X4y4;11D-hfM$zy@J(y zYaRkt(7g&qW;8T4E8o5qW3pHtp`q3r8yi4B%nb}uZ57a43~D*xN5gIey70q?UqC~f zg4Y~NvPml}h>Xnr`>7&U6ph)dK$2G4_BAk`At{+|Fcwm8Gzt+v5(wk$CzhN_yxq`t z`*75=`Gp1M=AvoH&9_F8u7G~Por{*1mUt^SfHl9b$P1bZA&w>sR8r2dUltb^mp3+A ziKWRnHZA=4k$*-=Ncrm3_X7h4&aSSsuEbMM4SQ`+d3lupRoZ-i_WSD0%zxF@)wPVC z^W^5{CX>m6TRU4T)0Lk-eR|W57;&^)89oV{u;@MI%KKWbgMi9vFeGHeclTjsPcAqKSnrp4F%!{u!cpzWp__cTMNkE;@4sUXDk*w zUgtrH3+nG190b*>DGgnc??0~=vi*(avQjS#}hK@T|`yUY6qubwIxNzZhR#q9n zlvn)9GL822+qX9#3Sk5)4*oC3@#DwAnhHm=UYNDIJNq zovR+EQZdU}BZ`kuD8-=t;zy;>sJ%R3@tfJxzb`lhcsGII$K3)kd$YSge6Ik8J(a@xVv)bzdV3&4+q(3E&e#&cR*Oz(=YnVw#f z6oQy4inq@1g+(m#i$XuJ+3W!g4_l zX$qjBfCXY-$ru8Sm&5EMU0k%Mk*wT-+#9aDewK@1?^v7qJ2iLK*Vhlq178gkm7qpj z0p~ze4z4~U{XS0aSiK@um203ALNiOgSLB!L^pj{2`GE`Is`{oO#G z=03?UMO-6Xn7uwaGSXc~-=a^LTUwUn=br~aRqpIL5mOPsqa0^yZR))iKSurGfq(Am z;^IF?%D^=)!#FExV(P;H&)HV=E$gPd?`$UR{aSo<^kERpHZxo<(7#pP<%^fZggN>8 znnc}_s1Sd#xVV^5aT?w0JU0DLvptLr@05~}fdG3t@N~cVQVq zK@t^+%zcrRBuR3_mi!7N9Gor41l~hlxfK-`GrNWCOWNA(!B)Q( z=S7r(@Zq(scsB{Wyq893YVzdw<>TkSe@WKJ-2AlPs4hs+^93<|T)?=-8~t3hAS@s@ zcpfQqfWQCqA_Xs4IWlvEiurQ5xUf)qQ?hpSu*clo+|u$gd3DvN1bAxsyLTU}Au~H0 z3v5c;Ug6Hp&Jb--bANJcAz^+na<67?yFF9Ub#rGSFP`20N7S>{UB(U0523b%Moo~q zp2nikXjfXZ^g)QTX2ju+eF_#gQU`)d2wKs#Uecs4A9;td?uPUNfn@LQ2CV()KFFD_ zuCC;AK>$%tk4`Ua4!}GzIf((zj`PZDZEZEed2<7K0OE!lgkHJw4umf)>L=heqzuR- zMcB1#*Ze`)w28?A%9EX>SbeX7>J@TROihH(90n@`uvgHr(O0MDEGEXs@5na-<&~*; zq8dGdr9p}VA~yO}QOEtLEnyZ1$-X^?hq<@A63?MmMshsJ*(E^5#wR95rlu-R$rv=F zZiLehLV^bSmn6K=S6bX_~3KZ}zr{nx9(wC%oHQ7{rsUWqo}QLD(q4 z<6$i=Eoa5VMr&LLYjHSUJ> zPx4g)bd7!GUDFZEfGCV@cDTIe-nNq#TI(i<*sI6BAR|MhQrcdQNQYvG9IVpy>%vcO zgdvffCNvlK9z}p*a|<*^m!}Xx(HxM8{N;-P3}$BbIE;5AJw1I$0#sc{zk+vE#tseW zCc{+WqSt*KI>n)fe_#H4vi=zm7_jB%btdX!Y0g3Yx{{KTP5z4}-=Kdhp!LL&o9Xl> zv<)5PJm4Wo8=pH*3kk(zyBR*$M@4?-0kpHV~lVTw;ID&zFHI4UA4;@(1JV7ceye0~i5==G4>_ZZ({St@mFd%T6c?bMx@r zpNxi*w-qRY5whGQJE~JGGguA;ghjZYQkB%FG z##jQQIUe1A#399J+7U#mii4TP$7g6ug%C(LdGZ8HKSXvGx@HdX6M7PYf`WhmsP5utymuPBjR*5IEes9Qw6(PlhV|IkBS#J&0^5)UIMrep zQ!QZ`+1ckLAmPgfd3SDTE8gl%hxP{MV=b|7LxAt!=-1|L6N!tG=6Wzx!G5`t5hEz4uz{-9#xa z1n8ntL{LCF1wjE>Kspg{lIbNPC7l9b8OhmBekWxl-3wqV$vcw94hFq|G(ROh1>hr+ z4?6f=D*Hc-;*03Ii~y32a1+I@q(9Kfk0FYT&< zl1%#ol4o^QM@+X@kRd8x1n>mOHNy@`+5^CTm0z`M{sG|o%I^n~e?>HY0x*Z<=aUZd zz%YIt7Qh|~;4%Oft$R0Y{JQ{N2JjNeEt2j7u#Mzx4RusF8~&hv~jf4(m6;;Hv_Pnz)Y2G0L~`)W|t0h3R2Ql z09Fn);41*10QeigNdWeDXyA7M-VZu4%^v}rL-Iq1x}p-Dfs}L+fK`!NXH}o^B&luV zNKR{qq+bE-KBT)DzeQ}4j%tY2Ih*A6ZYasR;d`xH0oXYFTF^Ni!2LCv^GH7LQ0FuWcaW0o ziW|tfVq=;WkMXf76B{Q_Cizd(6YK0;d;g0JfxR)A#mvvcsZMm?j%L79`gV zJ1uEmXs^AY*}Ud@lAHebbEKp%ZPhO#@Ed?*Nq#*zCh6@^`8dH`T3z>+WOJp4f;DH({Pm{c2Qb0=D4S=ly9Z}Qf>q$NmJ6;EJR5Vs~jwboB{W0zl zDd|E0&n3E(ZE!dy8>FP8Y*$1^A6-at#e{%d4B*a0n{9=1*CrtC#$sb$s{A*`Oh821 za!7JIZ+Px=04UnE!yUMQjD%4E8Sh)w9#tYTHU88bFCbgMtDagw7LZN^oMcKw7LZN^ zoMcKw7LZN^oMcKw7LZN^oMcKw7LZN^oMcKw7LZN^oMbwW$TCA7zf#R+$W5bW1+iL1 zS+fZ=>K4BiSYl*N7kY@~4TIz3zSIzcC$*$Je^jDq?FIo!F90|<0&^3|}V! z?GMP z1>3DKPUTvXk9s)l8If7bnI29%YRn~R*|e>T8_00S+hBP#t-b0;U^&S}UR8O6v?TVc z0oYw{XH5~YWGUN8H=HBcim5Beq3255q`BvicRAZ&Y~yF$w|Z;{vAs#DGA&gv7+bpC zAXzGPexu`^fsA>PnZUV2zT(gY%6eWMwMVBBod(7QWCK`rrUhgHSx=yJ?WwGZ*o|U4 zx!gCX0skloo19}Q9x#4N*fiBX+~KVk}QGpgoN=5l9nJEEs`vW%95$-%C{sB z$a2cbsPE^@BFMHc}dE1N%o0E`_yc- z^!?~#PV^2kzK(=vs sXFa!obhm>bA$R?_J>j9eCp1_H~(ZC)d>gkeRiy}~{ z)LMXMHVL>4xD=Q$gu~vF-YH}g)x4De&Fq2zZ|Vq9PL}jV6oEpe)&VrL8-bfT#63aM zUxjRLV$M#6I>b0jUQz$U7TRsb}!iNJfn_7PD{mGo(ZUKN$L z0K5=b7!mOzNpm9fs;RUE;9J1H5m8T*v^qksno8pVn%OD9lMxY*`RH?>+5qrbz+cfu zlMjLYB>ffvx~fVW0DcSjTgPa!gQQQYl8NlB(E!bCYv9Wcu^b}l^~iXut2G^~oLHGpQeGcZ39 z+|Z!?Ey5E4k4XA+I3nS_couLx;NQ9OOh`QVG$~uE01WVa6z!7j$^RRE3@in{0tV)H zeqY-yfb;214fzQ;yd-;F46qCEYJf@YV~V8DQX9E`5x{W*&pG(5V}_)kQz4cDTmc-J zYP6yHPf7B`MUrF#G_y1OW}jrOxylbnIzPv~EP!UV5wHsIHQBbyd`Z{l29XQ!3gC`h zqYY~Jprmt4{7D9AW}5-;1KX4sZa`blALC(}ie1JKh* z=M-7(edDE)POITY4#3Yta-fF&Ui9$~u)n0YYZy-gXlA}L<||}vmb)bRL$HdZ0*t9k zsp43dI`MiLUwH4dOb~5872y4VS4Oo})=Ao~rt_%)J>zn6O$WxfuT_#f!(B~M0lo@& zmbk65T+&H3olgb0wqnk&W*o77-6P4XTB}Jaz?enuYR+|`qlJ<#uGw!YK&xrI2Ypp9 zzDxz^kN;Zf^d9h|=TG6-GP7R5iM2UNk}s)Nk<=0B3*9GFvD~{(4wCdnE%T`W7XVdn z!nFcP=x2eXOA_=)RZRtW6>wWE<2~x0|(5ac3ujX&sc7nd!Nz%=wapVAe6ga)qa6{S`L(BnqD)4wi z0Mu}!2x1PvErG9Vc;28seDmVHV^-Q>X5*=(W8ioEAV7R5<%dlkf#E@N4RC9Up%&@B z5b6e_(*C&srvX(~wHgTaPDxkB`jrJRj6#2q_yc7;i7z_sBWX=+P$dBUkca1E4Yfqi zON-A+@iiM@7=Jp7Kj;wsoR_9c_Jd<^U{wJIh(42lxZ(8HN~$vTstzzf9B!w1lbBAdb;3ti00xLn zEyg@hl25y;NY?-ch<+(2y_|=r{P<2kiNl|g{_&zFKl)^IU(?O3&+(1cvuhtx(N%zh z?ulMoJ*C4>?)!p0)4f8He~&1k*xffd9u;{c)YX!@^0w4#cj&UF$vmnZ@^fzdJrV&OUpe^HobjiISX-oQQ~sQbk!ohluF95E0R} zG%^yxiX_G|jqvl>>y4q8uA7~guce1Ak(`a2l`V@3(9+&k$JWxu-@V6Hl88w6mWqO$ zp5N5wEa|)Zqpe*#mv*|!4vKkZTshy#$vEqZ%`1=YDU?K#RQguy4`uJ3Sm>9hgY{AcsuU!Pllx>%R(qi`ak58d^qJo=xa zdujYnD(aL(MA{6pP-tZYBo@_v zcrDX7yVn{fSvEF~)9w@bu?~e^BOD|Fvy{dBpNKG4k9NxQtAWfDqW;DZP7_gbqjpDAQjSp*3dGwLSIYrpO_n zr{FyP$UH5#mlzQ#zcIDz0~_v!1YgrK02oPzt1ho+(x%@dShTSj1eD3 zEI=Zo{L6fYGz5^>x~*lPn-%Z?Kkn`kG_*?p)A-U^X>X^~`KaOEMjq2QfS6|UjdzO9 z{SJ$O?;nWA-rh=}18g*^55_rZ}WD^RUn@BvP1 zgi?2JihUx{i3z80*EQrB0Wl(ukBVsV^CC_&d9_0rcS;b9Nt)^vwsL-M3s)cNS30^Hssqc?90j9f*?ONzu@(Yv?aM^+I0qHk|cUJ`!m z3W}$nEgxl!wzE=k92N0%^$F6lW@7$BWZt7;3>tBhiX&84{n9?gG354+sljVP*eK`a zKJ~g`k##Aa@=R~dz03LIErQvzDZz+Z!)9xqU>d6`exX?tDmyr5ho?LJ)Q2n1uW!-^ zBM$z+N)7{!Eq*{2-s_LDW}5ut)I%oh4KppgfOZ)}ih2_lfNlcX=fPjwCs7=1D4RWR zf<~nCim{9P*?@(wS5X=FN<6~PmGVk_n{QjJna)qI0!FRUJaX0PpYQALlkb=^{^xo~ zj~~rHscnmll&;W8{Sg|us^5uG|F?B0Ji>2_EvMUu4*P(k2MG zPt~jyz2S>)z9K;dn8<|RuH4-54z$r3>lp_FVfokgjj~LULE)H z{ml7=H8TrTBytrWAN(K;3k|lgW{rO`Bl)7@7Z~YSQzA@x4A`!)%4_ocEg5R@1pQA9 zE4xRXiVHwZ8nsl!va!O|ne{CSfX8deP^BLa|Jl^a>QQIFM$XfYr?jx<*ZyQ{8wm4_qW)Eh&F z9j>eE$t7Od_lM{Q9o<}Koq(bUcGlNOG;BY{N5R$U3cV_|#KBs@LIrw;>FUkznuFd*v zsD(5Xyt(`@-zr~MuM4Ob=(C9^u$ug5n|lvo{4YE93Y$#JV0tefQGU5b6uCk)b&a09 zxUZr#;8+W^$&y&2ZQJ5ko1c@zM+gjs?DZc^Ow@NBYKD4T8Pu*2iEmT_T@Z|re;v7? z$HQ{P|Fg}b&V~)kpp>2%)dP=zPEjOV-4syI+Gi8^)H&#%H%2}iq|m3XZeRK`pqHJR zNQF?nqW1B+I`6?Yg!4@bN;$|S%9A+ z2>4s=q=8(pdf0LDDoTGobeLJdez(K%C2mZm&JF|dSF5t5bhB@ETVM<;?{?jJz|Q|d zC(k;&K^3&fs1^by7c9?TT`rCx#S)hwHPn&WQ{l459G@}qT- zRy`VcLy$Y$`X+>FV;%sg^FB^M=wH9w;~=aJlG4IeZ! zOi3i$MJ8wxd9=k|_;E!Y@XUVL{wf&^Ufe+PY|P68guRXv%Jp9OG?-HUiy@wmcFh6L zb@S8O!DnT?A)JFHJv9SYL7aU~0=YdJ#^lA{HLvOwH)(MblAn6)@A#a8NcQx$fOyAD zX>T5xxqe~qN6x_@5Hu@|prGt2x!~+*yDn_=@h^lVOpPGdtlGuq6{ji9>`4Ww=91pz zyRG{+v<{ib{Q=Fis|!lmlg7<(7bQ9euhoE~nh0-M0(+Ld8_1B2c`3k&;_nWsk>L$BaR(M?@d` zZVNVS%$oyZzAh~DLOp881=X>@maF@1cZqDS+hm9$Xf~HZFrrvi7tJ(&l|w)D-4YB) z!W!sJev=(}0KD26B`aWhB_fT3UG0(Vl|QivT#s(GJ-y*B-CH-S28eOQeGI<>ARyu~ zoO3+#XjmKk+-}EW=kUP{>h9RXD=NwZvH}WHl105f+WiYUw033d%9yCD@E+#sTY`Dy zoUEQbzY)!pzKe=^;2?Mv-O5?A)$o9NBT$ZZzf}gOQjzT5TP4%bD>!wS?eyV zsyfgNv_mlW)*9w2TgvYK?hDwMe+lRZt+@^z`nLGtJe;hsSHY-kmok(e$Y2MhCuT1H z-8WKVc4dklG66!6Q2+}5yy$aK)e|$k32`%QzY^^pqn!t;{VUA0;BA&@|A{Ri2Jo>U zp?2oMzv2Nct6RdP`lf(OzIdd-O;Yg&Z5MAobS+hX$^jT^Sl zO5b_Eh)^c8h);J~GF=w&tANaI30D4tZ|#2P@re>ZOzBH1ZAtHO_7%ysm$AFvjf!^E zw01G9gI8XqOq!)AdQJ!E*R{P(YqwHya^;|T-zTXG1@sz}Kp#2#X7b)j6~|m*E>4Jd zi{EN{t$u;TJleKm#ZgzQQ`)R=sQJZ&x<}fe{N^Atl-Hh-$rZ4}QWI;HshRNebvFPH zz&0{OgEp)-N%CLRyHEl06+ljaOl`RW2q57{oa3X<%7n@QUw=kSM06zs(fTK0AtL_Y zMR(cyx;i^MXB68-8|qr;m1F0pekWqL793p?3OW^EQN${EsLX;!rTa0a8=+g9EHNLcX?Cw;n-6mzCU2NaXMy9Zkrk znz(rrp8!tdaD|)Pqy;bHA7>s86%6KnG6kwfqsGRR&E&1Y&1aupjYdaXd;Ce6c%gJS z(9>_;J9dNMV0pLN`86TL?!mg3tkPE;?<8%!VYn!m)r#*{-sKCXEKbPdkGNpLw!2pKV&+?@(8 za-5k4;h7h_xBgzAYHB=b?8)4mxl38=tz%%q))IJ-d@YQm=P#?gH)V`J9$HtXLE+vg z^L|}gajL6Vo7uXZQX~l}Jgq10yY)nd$M`uSIZ&b;>9I=X=HP;Y)1!asfA(Q^GQ3H| zFQ*>7NZf-Ex}gr{F|%3C@J(+1)L#{ZCpXfq)!4u|y7DiPg%JQ4A>z zS|7`=b{Q*IKH1-V;g$qa0gM{2&(t>QjgM=vpgw3Vn(_jCbZw(J<@MGJ-)aKHO$N$! z-%yudBy!3W?SAF-WIwT-zUIZJ=(9Ceiy0%Jlu7Zpu|W_jtG$CL*U>fORv*$I>-59F z$Uu#aA|OJ=%Q!%Bo@RL_ztPiRse@lkjo&y->~M z#1!)zf?9%4p96XU{-&UaKhw2>M8s4=Pa2qFl>!d7*jfS(lk!8k6Hgk&9e3a38^m&< zfE@X`VVMg(3>3bmn2bKG4}DQuekTi za3uu`7{ZlGNWJ8~7nz2W6P3>L_ou{k60_cGg)oLb1LB{7*@tWWPJNl4l$wd~)pr?= z&#!&6^yvmK`@l;ivTu+X=;odZ*Q?U{k|(Q=Pf3>czJT@hRUE*^O>z!!!-9rPEiZFR zLJbVrLHkundwT-Wfc%7m`KO~rxwVKYhO{J}|E9U2+O#WjZdw;Vpt7`1a95hzr zSB#+>y@etAPdb?fOJ-Hwj@Z~?QjJ!DvqLkxloNAVx=-2UPKKMmY1vDo+5k|xZx7%_ zzktduL0v$q>*;yl*Y~GcP8r0Vo^5(|NE0_iqF5h|Q_T(b7ICLx#E&i>|M>Eki?ph$ z-1?SuF9#;>w;oYUi>IXi5I|T>=Z?J7SvzWU>?sT0PBF+F&Ly$h{wp^O*UZunH8C*D z*j*NwX3W35$nFS8cfE=ECVH0noq-tdw;5PuiHy?{&;xZn6#}P!ayLQLlyai?YHbAr zBU5HSj<@SgHx^a+#Ke=7?1~cSSWUOtEABu%REC$;+Q|jy$Rqa~N^}ei9v-_v&84RU z-j2N2wY<5oUWe{j5_KK;m9h~7MScc^2fLisOR{%7@>S9 za7NzKT&J_c-&@D_5Yt!n@*7^kc$RsMNXzE7pvH~iiRYiR7^sF%c4H7OL|ja z5z6@8b(1-%IaO~*mjYV5{nnfg@+U*#OHUl3_*6Ein?$4_nUFn{UoskOz&ky;(1MlF z?Wj`vdXNor-2`WfJ7o`LT|xybumJB*34?P^3wQF6%N+1&#_cP8T4E%j z)PfpL3M+;qAmeF+_^|aGsT=PDGoO|6%1mnV?|aVkya3(ee}Xzw1tAvBNsQQ!zBx|_ zFZizoyjqdE8*5|^>&<;V(7yf-W)Us_w4iI-__Uup5f*woKmTs-2xHNu_}=UveV%xW z0cPjG7Ach8@lDz$kG8PL;70Q4RCm$k9rCsC$%0PXQQ17{iIP%S0?Zp@ddhPlIAdg99jt?QaP#gT4R5_TTt_2*AuN+y_SZH-K~vV-OJUfB6{Aq7T7)naW`m*jZv|XFW($6k z6=3U>rJ??Fmx<_JyQEeif6gUHuMnbO8Zuls(h>}tuf|Se`u^2_kMXXuRw+QY+>x;XJF9bfwuMR zuzK08hE@u6hUU&~5m3lpB&{mk{F+3D%gHy&TLFR5B9urzP2kENojfJel1ucP;MY%wXTdCvA! zZQ1#h(HBy|`1pFot+*oFar!FPne-Bp_KWD3?W0i&9(UgEUwZ*6P{pva4daCo4&?|i z$)t2v74SAFVG=N|J7P-@+)__LXj+aXaxl8CX4*|(VBfmTcYd9eyPnum8Cc(%`N=!k zpkr*f^))i|>3e67E>n|OaEYfIjX|vWoD8ZYvEXzx#0@L6pkZ1xglJCllUP;_*VfLo zR62ni-gnv*Qb---Rj(0xo-fz05G=9j;Y^1vozZX|<*mkMXPik}H_&i1oK`TuxMT`G zM?Lxs;p6H}q>@)5p3a)&?Gm5N{G^`CD-vvZ-uc^XgTe>?G^c=ZDA&vF;ht&ZfdiFb zrih*DVqWPVzx559mzNH$ziXD!%m~4CLqK~gqXRUr%+js-e!l#g>$*yPeqH!I*rTxz zvyS7}vVxz7O}@Gi?k!!E^(POxtvmvOu)Z3wMWPq*e<@$t&9ZbRz!MwJ!gOoq7G_^$ zo|iPYRKz+9n($jIfA%}_r%|`yUoX-#rh7h+l&rm$$nc(mYvxhvpss6gZAos8iRO^k z$=Z^yK4&R?p;BX(&#nn~)cb%u78FG!zDof2)z7=+A@n2TT9e1{h#g<<*$NHtgNOKs zX+6hjj7#%tlP7_One9BROH>vH2b$n9%C^Li7rlI!U}gB>BV$ZINc6a zVw_EDj@F9dDMS`~2Mf;pmODd@jIzWlej($WKh#B`Y-EepGkzh^O*euK{@lwFreR-b zemEgZdM?G#hYl566npuhI3ox=PnV%IH6dvv6zu7O5RgwFSNs#Ck3sOwsd6O<=R8|` zKKsI48V$s$m=dBv`O8?y6H|!}2OM!ajXc5hdJq11c4upi2!(;$xGXGcapk10Qn3f^E?r;b>M+`Jhn5%9 z8@gkX>H7huFNemx*i>@tfF$F7C0JQB;7dngESUUj=yd#VmXC@H`{r0|8Puyjze%z$ zfYMjYxe6=6?A+q3(|PiWi`fgP4v9Ly?~=5Y#GBQcFLB`+%o&aifB~8BZ*{;X<}a)R zB7xokakA!g?=F{5)jtQyXq54@pk5;WY9+$L%$h|u)wYt5GJ{Ebivy8y56X_l-*?HQ zXP=D#NlOTK1b`|0ngvp658Lagqe~N%w511!zPdMg%_1M$=R*AsbwNVk}*NGggVM2=e$7_oty9h_8G9>@PdB&-eae9oUo!gzw zyuHo@qF*ocM336eY?$kEBs>2O-wo-)@}SPLd*>cEp54Zi>vpzLM$cV~Ufg6~I&q5K z_igR)`Kfw*n`{5JsC6k3Ls925 zVCN&uI}Q#kC#nyWJnts3!BPB{zlk@-Kz{b{w*l5fE!#b>8|NZ;Z+&jsYgmF?kjwc| zzQm?@!oHzW>!FEm{&E@g@mT0|J5q1DPMTZgbh^^z1(p}J7XEvWpYP*_?FHLc+)+}^ z%n!qP-wMv;zb&Q@jO;mbpB@9ttxK8r8G1^tadW7i#5QNR=5#tnho9?@iczg*^|xen zG6j@YMs+w5Vdi&rQ%09n3K3qOB@0cuu1Oa4sodPPg3Gt2sHdEhhhpTT4;@ItAzg{b z=GOVk59H4sa3VQ!`9Fb1;wIh*s@w?Wd zNj@(T51gF%9U2a*q|-Z&(zXmFEJa2hXk5Q3A2|5p);;pKK>K?F6ECMT5~%2|)9BRV zDnCWb-7@OaARqCaPI3H_)=O0TLSUgDd%V@mnYdk>8jpJB!4F;$65;~*%Yt zlEoeuO#SXnClcbW2Idq#m0jC0OWqZVqo@7brOU~^cnu%0>B4JIqr1o#tH!KQHc`i& zhj3UPBILMT9QSNzNaInz#C0kM|0Q>hyt*IHqQ|s9WX~5vD=qGE)_k!M+!PN zAl_KSyxsB>|0?D8tXQ9AOtq+zq(T-8+gx1C(+z|wSh(`kF{&+0N6rM+Xn++fh;fQJyc;GMJ%V8@Ae}1eSdP#KEChdl{;Om*TCx9 z2KC)aQUok)I;oi7n15UsEvgqbF*!V3A)wWiDmLaE935VGlk8<{ynS%6_}Y<=2T*KS z+qO{kl);OiDa^6+B;ax=44V;i(^;8moocj|=8)1`Yx7}iM#|$AmRC}0lxo^3I4c+$ zmpeO#BBmeY=T=;gx#T~Nt-Zg+AJ3a|pLD|R`e?_otMoRth-wK?7!^{__w%dDmztW~ zTY}`i@n~U-^}l=H(am;U^YCcm&vaOQK#ZQP4TJ{=hRr189dRZnBS_|r7pHmUXh|o( z4kV3%p>M-bWA~?j4?XWWGs#~Z0?4M?tdxQeVK_f;wTC<%;*@u)*}ZBn_W9Z<3$Im3 zx=I9LwhEJl+16*ZL`MqbTnd$pb@MSr(euoJxSL?DvBB z)X>Yb_ogpuP`vw5-iC<9C1UFm?sQgldG#;Ek2(6&t ziO-E@z6A$tWT1^;c+}}@P5;P@t{URdLpA!ZW9w#bdUPBla=nLzg0erDJbHIZ_K+pv zRZ-qZwv_Jm@wbY;adr2qwocI7g8Ay|>J`_xb=Do_d%p2#swJx4*`)N!&`>$k|n?wa#>lFvp^lvqjQlv{nThlv4 zD```~A#$|BU~sjd$io2gvapp4vz6Vm=`B7_Xx;E&Xz2)pCWpay&{8M}GG(m)mJFXE zt`Z{G`qJn)OvIrS=2fXbIA+U)E-3i>y0|#zL|4_~rB~}i5ARoN z9ArYkHNHwhOXDdX=(1lreez&$^%z2Q3`rMZE*gO|C)dP(5EhbEjG)8%?;M^T=RL)h z6_x9yX7~CAy&>vTTjVcx>=;>c)DX3Syu*%ZiK$)&>w%_SD zqlaC~Mfz`!hDDT{TW*f;jS_|&LMKFK0Dvu1MfE``YDF1!|1zQUbyjO>(Ck#LUBA11 z!{moSRvV=qNIopx`X)m#=13dn!X58hRiOD|1-hHU8LPb8>2W>oqh1`;?M_dr5nN-V z$pCc zMZW7{KZ+WSb=>yLYlOXJ{OOsOt!hu}BK~!HmPI{U9l7{gY=EuuOM+Lgpm?*5{c5CY zX_!#4K^jKcq6(oOI)F=(_|8qn_yjfR&iFws4h#LqDtOSL2diOFy}wm{>v@_2#2P}t_Qm+DQ(0k2Nvone*S`PV`lb{4L zjquU}3tsO43{`>xa-RS?m z{@(@rzx)4}_5Vc?f}2;9$7v`=KeCh-78YI+rt9pfJ)l$ypcsFU>EioP(>hU5Ncfwj zpWZ8mff3LfAJRLI!6#V~5++quvk9Y1P!IdZ=p{S*f!-dEbKdC;v+~U3UEbB|A7nzZ z4xEE7h9eXE9=Tl3pO=;&EE^k`XMt6oN`j~{l?-wD zjoD&2#Unvw66(h==l6XG&0247tH==+%snvT&$pX*+;yhkw^Q0yBZWcz8BN`>-yif= zEhCVHRWl9k^mO#9C>h+lH?;#=BdVZTy~nw|cm72`sDEq^qi2-Us|h1i&>XNnfUTIt zwN0lDdJbjtvd0dMgx0+TuDN=N)+>1#VbBeMdFxmha5>pOi8s~m{h>}7cT7Ckzc%~- z?0?skYmLZ*tN}w7n?-yk-v8B*E^S+H*J29F!gh*`*My$`DFkWTo(QO@;|;zEkqAWx%;zg*BVW44Wr0yq7tMn^6=MxfCGDv_9GtyRT!fsCsGq?7-W5 zSi#zY=tZ_>!M@7qRm=M{e{Q%?(*h5*`YJxpuQ?X~D@Rc?u#OW99cJ%GXlM62!WhbX z0k9d^+Zz^|T_Y2N@8-&8YfVIqYU_F_1f!#w^eH}|u;9K#SOCkXQWWtLY^a+YOI)mRC-%V?oZD#7LCBq*=$>Y6DiC+NYRsngW+yZcKnENXtKk(hD9*A&*&n$s#eA>4Enoj5O-3*e&P=uRUVA z8|*0AB!(K)=cSP+j7hOPfSDJD!e-8UHJpV-{E6kT+)dGDFvLqUGy34*?g0bCLF`X# z^0jkeP;QKo;W7hLE=%2TXO#;a{+m6MRrL!GPuj0d9nJM@ z#_@JxU2DrD%y25H! zGnK^1LqYocWgPIi_bo0}L*a6T(Be{|K%T9!QWFKKd(7^~IaTc`*rgKa!=7)UoRjTD z*vQHL*wHxc97XsC2>VW(tppFp_*fN?t*2!+5C#yOE!3!=iYwF`QW7wu7m)aaDN!(X zim=L;(fvIw{8&GGC-csq%Myq7))w0pEL?fK%xpUS<2ioZVz<0C7vJPpe^- z-o*?MZnm6qOX;mo-O=i9S5|0Gnyqcs@&l9ZIM%RyPdT|_e_+Y);V^-%q#pZp?zYPv zgSV}IQi@|IoXyc(E04q-ImMji*uXuSzm$6;I6(?wvgE=nD_4AMa??@ zkXSR{-+2lRwiW1-h_S*zp%z0Br!_Mdd@2Q(`VN>j;jXzF;+a_z7IGM*W98` zhF=_zSw6gM(wz%gHB0rIspQ9Zs0_p1m?yU_f|ujF?0UMgLO2Qaj|VD#5xMSN_<_ry zEQu{vN+X<-kuwl0rcX5!dbuK-=ZzhHvvuA!ZTtHcM^A64KKuE1=FkaNehttDgl=*# zd!Zib+oa8A^Si!%8A9)xOdEPh^B(fOU#gwrZezw!sxGg)Bu-s;O8FbLRL9v7@Up2K zxknQ#{@b+NtuK}9HdwF5f0p|8!=Ce4>7hfV_uaqvZMIpA;I3w-@ItR8lTb9T=}VcH zF}Tq+>baoFpyrJe`Xyuo&byDTV9V;a_0we%k`5-P3--B~gCFn!p6OO)5zBYOc=zN2{3*e-Q9c>O-#A+OQxYK@b{M}GNFem6yS`i zw_Bi+U{tD02qvD!Gz`9bu{fx5)p^~S@ec2n`EF%o_n&FSz41h*dkeRVJMCb!hSx`b zC*^5#N7qFhh>nt5x3-A^i%jiqJE;(QFDXDL)3L|en-cN5&lq{ng{Nyq(dGfmfQFMs zj4gJmcDSKhWD_+y+B4&eCgktzk2jWoo*#lpnf X8l+7%SRdCn~5F-8R=+Sn%A8; zPx1?uuk(jqTyIzyR?TxZz-c-rMvLgsDvl z5(L33v>J-EeTZN;Fz3!XzL_?T4(Xy0I*F-Tkf#>Dlo;gSFj-pVK5oSSw30cctm$jL z0S&$3fyVU7m!dKIr_Nl2jB!ZUlxFn10ULTmeAdLv@I`87I{8h!JYb)6|wc2YRILO2g1^0@{2Cbg+znSxj8!J)YXPoDE#R2?R91+FPs&J0;5BJ z$lIzzC+LE3>zQ&?Rx{S$>^%IN^AgqEy3drVXc94l`t~0o(i0sXKgH&g1WeA4y0Qimzn9k=_e%r6J)ZFT`8aeYPuRY>5yylE z?$Y0XQ0H&XJ2LnyY`NFD5dN9T#D~(Kt8M-$C~h2ko@ItS;sfU>QHw29$TJ47nBd5R zLEfjcN3MG!=jLDwN$#~O(tzj&X>@?p*=lqdKfC)Z>oKw^aNHG%5!)~#!AsKwuH644 zzA&uY`5BT6=!vx>DZ9Y~q!R2zBmQ`fq+0bk2b)cgUdkj824n6(Q=8$z z)Hc!|G?C1X_XVU%P-*s2e(_co!}X0ApR13TtGVwyx>&M5Vn20SF@Vw-k2WLIq42<h_pQe)AV6aI3lBn+w&8tkAP5enI{q z$xAfV327gG;LmvjHJCKvl-|iMg@`PVrNbo1Q=G9=DQa!q^pR|Ipf&$R2bWT)y+$Wx)L$47G`t&MevmZ`iQvBUJ?aw; zV*Y5*%EWIqQ<>Nonibq@BlhIeMTmKt0DSalHVEHMcG-GSe(O-YMXm7}w6RgKz&zj! zu;EgpTl^M$bH0BoObYEB##D8%UHZhllQoP)tY2`i)p*dZCB5i+j%yNQciH9KWP01c z5oi5W+sOM=?wJ~mBN`27wI``#>&;Q0iM2bpP;1ij@EFcPwR#m(sb;g@K=Z&S@3I{a z2cGOSAHm@IN;m1p#}?%^ahpWt^dgt>9R~nQ#-(#!sY$mCsxgbGmf*KF=P2Sv;`=JA zfg>f$FDqXg#+P<>8l3961eR;nihY&1Eoczy+jSQ+%Xy-~*rX|Qsx}vV8KzGe?#%)% zF9%N!NOQ&ZL2Sor(*38P9w#q!KOJi4T6lKLk@o)h^L+_f1&7E-;~IJaAbw2Gqz4iN zhUaaweOpc_E{|>SId30mB{Ss`c>m$CL(Qsrkdb-Do{vszr4RsM2UO-e;m>dtEDam` zM$2WAu}-HyzW1b*9v?1FU)T0*5aTmpr{~!numdt!<~_&+Yf6n%q-F(c^H59frUbPe zcNNLt%$sm@70;fa2>C$0c<+)W%Xev;d-QKNEE>wsv-D~+g?2}M^ay-a+8r= zoU4?TnNLe!<4(J^in;wf9T~@N-KyiS5c{!H@dh=)$-2JW5(tL{rG}-`4RwrwLG>#u z3W-?CfD+zm;K&KpnSn)M`=;K&B~Q`JjHy4?8|N9&-iZ51y8QPBRh+u;r0Ck&K|WNq zKs}Keb?y8pKePrmIRvhi#_tQ@ohLokN2ck|;E!u-8`QWKv`p@FcUV75i4~We+55|O ztx#;WCXrCU&)boSxq8?DvEHS>NskdlX}UW&D%h@ zdAmakdxXU8!?q6VQ+wXp5Z+XuRyc`VaA(@(z_e_W=W4%Q#nb^>B6T_AXb#+fYb~c* zi6?F2;%(?w@wVc%isF`5m3$97QzanoaP{Q-+;e zH{%5-qOBLrJ+nGyHP@M(!k=Q0`x#ouknacw(lDC(`ol`d*`(W7o|>DfYL5zjj?2AM ziCYbpv#-m`E@!kzqq7*uAb?gk5#&&OXlqjzBC<&!b4!0|*ySvutTo@KWC%j}~TOFS1?ZDcay^TZVF|{m>at?l}YW zILLC9{WVaX4xwW!t8*PWwwUII6@I`IX`mEzgQmW_@$kn>V<_zLJUlxJj0P7yJbv+z*JR!>Bou&N@JjRO?Ws76bg+G z?^LV~FQ}}P(~z{Z>iDS0)y+%RjBGO9oyN5h>aAoG7p4(=a=m`3^ZT_ka|oM8=G3?m z_~fJnFWAC>QvetE6BhjViKfh9oRR#JGs;X$i-mNnEz8JyF)(_|eX=AUe=x4Umq3JI zwM@ot1#TrG${H10k3B+)+b;JJy!@&+BadXx>hW&kp9vmw(m)TG5Vadt+4XM_fn!3x zEgS^jzwbw%9a{Li_#Ee43@afDXk7GH`AmRogu`QQCkR+2os~Nrr0N@VGVt2U$ti&2 z%*;!(UAGJf{T0aLiqr@w^gGALmfnYT6+MXG?l95kbvo9SI*jBk>u+h- z+49`Fvrxb;J@sgg>5H`}8{J^r0rK5!lW*d|?c28N!!kCQc(%S9duQbjx(zIN;}SJQ zVy10F8NdtN z1?h5{TU+A#xap)Mn-uf;QHIZqxv&9d|5J5;oF`8sDkxy^!^O(t*-Z<$vlq8KXV>DaKA_`6e?d-Huaulx7#Va z;eaQ_)!>^hD3mlIGb_G#oH9P2ohuB+32X1g9C+p}?0v}ubGV;hvbg3HIRCg2-Lx-M z9ZDxK*!;>Q%6BVje1@iBbL8x|Y__!34@MKm0Aj=)V9_nFH2((f?qerVuM4Yv0pOcU z7V>hBy+#<;fGnD1;jLrLX3Fg;QzbM_ZMN^=e*|SR`1TD|9c2dgrg^RqQtbIN4vq9j z2d6%y?Jo;SpfN=DM0U%S>HP~qf4`|7I9kG8H^j1b8&}_C%dY)<7OAY{?lcVUYqZh zb0$*4-iJFk7Y1GkZz+Sw>nMBZaH63=_v;pk<3{i-IWI=7gc&&Q` zQ+-<$gS2VD!Nv>HSedCEaf$=;t4BZyY=!*98_}KP#ied5dpQAAjW62M&C}4QF?2Wl z>2G=Q2uR*dy5PwkPgT<DJGXiAhldrr zMYcwttBh?^2k;>bamHlZ!(r4D7McEAVtBkg&QRlW(tCWR67r+cV;OCi-A#Va>H39~ zMqo!VZ%aMiV^WDZbXXGof+OS6VRpGi;SFUzu(>~|`7B^t`iXZF%VnJ$Z`(zIaaL2e zD<^6V5)CNUPoso-2hyFJMzT3&vsp|Gl#ib_dXH+Cx2<^JUuhvg?o<7_REWMhW5yj3 ziX4zQSU}|MPZjs6dNf*M#YT8yg4tpmn!Ya8Eq1$VOYfmxtQ^rz3S&1t*IM1viN}{m7YG^_k zd18!?TnS3uHadvGe*XEM&ge}s$ z(yhXgc}F*U$csCcNrF?$Z%DN)@KN3AYF2MO3N@n>kTI$K4zu{px3#fr>2Iw8y&!3H zRw!O*%}YE^p*sCYdOOaazD1fZ!}sv<9MS&l(XUn7H|pwpfrRS0`990bgRN2By4r9D zzZVWE_fju=NaM#p5c9cozY}v`aJNetVe$`3ZYFd&QQvt1zgC>~aJIZs90QVRg=D}Ts=TVOV2f6| z#P{udNhX+Q*5k4M{CUpKR*y~{hZ5NNvqLY1L)+Fq?Ja*!NL(&3aZSA4FU3)}y>-vg z$;D;QclM4Eb?)GFV@8uL$BCy}mP!*g9FsD$u~q_XxsJ%W}aG4JBXQds~Y@x#%IRzbY%cay$hdnFKj-BzymLQWzGyW z(mi0N{MhS4Sgq8cg{NYhf5Vm*HDwZ+AMhkq!O@P@7gPEhkAbgKa*ZVq@WB|CO*f>Q zlgat1i_*USYog(=N-}l+Q#mvP7OOZ-w zKbfB=?HQMi5@yeZdpkNYI z4yR`3!8MVqf^$NiFFnyxgc?X4R$G z=SP0vckt-7^LUO-a=l;i_2_i8_#mPgxt6TKbzs|iRbTr{qzN1`3RJu?n!kVG1OI}p z(tAodyXb$r4tMz8bzK9emFq?yiSW+R|7bUV-LbJsbZn@;-6*91N@`;mb_Mcjg-O{1NmLy&`SIxOdSja`zyTm6S{(p}#);Wv(mAI>TMMf)_b5 zo_49F+X&N1pH;})94DKQ5IzFE6a4HHJ&aif*5+Pf;pn~$v$l|Pn=ULItdgji94$Om zallWg)lpecw{mlxycHmmLkJ#F)-qqPB1ey8WoIDyxRTiaXbfwY2DA(D3HAkF23;7| znI@MT4h5zD@Eyq9UEN*&<$Q}G7dzS9&bM&uRewTRv9b=RCDLOFyBX(XGoP6`hEMPE$d%Jf;7zAY-eiG$r46MKA1clRC)QqBqF1MRn`=29 zn)$q$Fy=(pd>qT&a#Kcsx)?4lPi0lAju^i?T(B}_ z{aT%HJJDQZbhw8Kc^0=TXjcB_)yrHe1>0c{kzt0msYe#==(i?Vbw1F&R&KSY)=KPH zkK}n^{KJvQ@vVWY%(Z(ap)Cj3rL)o!xuT<^?LvjBv-}m!{joxmcBtojF9XKSd~bbWlO^b-aJg_D$EF!QxIZTBqn_sYF&5Wa}1v> zNbB8HVv0MOyVP>ca@W)o5*ldRg;3Cocev2!pZ?vcd$TLdKmJPrJfh*ce(<-8ot7WF zTMW58T~}hJ5O-sY+UMW!-k&JpSUQrq;A^vPVwX9$kp@RXMA1P9fybK8mlQi?%ij-c z+g*f&a6#MJ!%pmB=OJ;eId)|=QBNM3Ws99c^@!W(9WjeYNa5?k8o2uq)q-+8n1@Zk$EZ z4QiD)$(+-@JH*baYKU$b;^a=7aJHVcTl8n!&QV< zQ8O_`h|nt4Q?!ktN({$5MAS?SEv=!3AS9+pOUy$MV?w;?dDnW^d+&ShTJMMZ>3-cG z_FB*0&$IWl|Nq~AUzcR(QaHYdj%JjR-Vlz%1d)x>oOpys0X`%OB%4{iNIE3A2@ zA4lDg&wTU4jz9Hhj>stCytIUG_1l?U64&K%o%QwHY2)u`;kRXq27fr#JwFcD-efVB zeaCxercd>2c=`v%ceuM{DTYi!QDVG6+`?_m@V0BU;_D9C{O|JqgS`_kJ+y0LT@cT55c?xGLj5uk%WckkEXg6w;irj4XFUS- z)Z}5R)SwED^BRLhM{x6NG| zI{12N_4Y#N&+U;VV@0}V+zt?Tfb;hv$^_^dd!1U7~O#E zeFs>#3uelcN4z6L9=wA1XnTrBGT|v; z!(P-?QawiB+bkyf!}5dfMCw=DSjTM0j|=l(V7s#Z-ud{KyM8;+qdK!F*qQe}7!LW4dPnZ(4t_L#jUJ^c@^c9Y3RYG-X_Hcc)f4G;Gbj;pi=hJp}Y=Q|MOo#vIxb zB?pklrY5s@Y2{N7tLCUxlYelc$gPa68)5M}@V2*oG-NWPADAAj-?hK!QbkucJe2IpCp~=ANjcy;cOxAX`>uG1QM+iOIL7TMW$!cc z>5Q#@(Wpq^ip=xQaex~<8oLqCq(&4ElYk3KH>Sm|cO3;Z4`CLtnz2s2QcXu;Mx$BpblM1U{60SEpA}VQy z*wzi#vEZ#67@t$#h*}D1#Zhj+lLWTu$(u<`9?Y#x*lR}aZ)-HHbx1g``2neb3HS;T zQ)(+S9jzOwokm0+CXSEe1VqADFJwb^9fDKre3aDZ3%UCIQ6^7{9cO;jO(D!7unpO< zN6giqXLs)ibPEEq+qDwVYXOSUSzjcrHq8iB5)!mOsC(-&B14$ykm6l~4WWSmCG@D9e-R2ZIacd&i%lFLL58`q#K8xxu^3VqLa`O3ROOr*x*Xv#|-nBXdWGrB%S$n{pq8}?67@1)YwWNoX)nTmqf7AYkKhw#fEG#A>w0l2Sg1p4s z^YLR$7pTS-K2F1?P}P4WMinUCT8DkJmay^|Ato99Y8c2h%{pE0+{7D)2+tpzKCp&T z-teo++RkWcQo_DqEFdG}S2S69-J45vyvcmOcbM^r`ZWe|#ib|JF66ct+a0>CeG<_6 zeoT{IO^_+iAJkr-F;{w9<5_aLKJy^}TOZtL@87o-9icp7TW1QiukSkW*&%xZRR zM16V^0q-#t{H|GLd@Z%8v^(j3F1h5Du7h}5K@T(kN?qRNfj!P%tJ--JjMT4Y7?V4k zp3WItn!*d}0YkIDC%Bo#c}zpEG7K_7Cdfbiy!P%=QRo~gDp$M zw{5ovPS8ceg~Q3r7w9p3vlIb~D=<58z)1I1fNu#SC1-?q1>h=AsRgvT?lswjxFp!x=D?e#fN z`3T1TeB>i~3lD%#)3I6kmreHnb{hYhZ=+5iZ4<6N*zU1#KesN{?ZJu0i(QST#K!Cb zxvF~c9S^z&qP3s}Ut#w_xs+A2z`QsSXpwOUpVX+l6=g)~U8n^%lDh-l$sHY}dvcj* z4iWAXx-9-kkg@4Rn!82JC7BLVCz#Z>-8UcCK*ywka)7UXqE8S!ptyf zm!7L@Z=Uyd!c+{J=0ivFMbi@T&Fhv%zJiQ^-lkoSeVsO=&r!rxP*%7IA$%n;Rd9bF z{bX==s^vM+nl*De4^cF@g?NiEnZAdb^|GA359Z7?lD#~v^bxQc)uDIywb}YSF=%dQ zqOH(m{rD#q!SzYe;vcu;r%d_I-8A&@^vEx$y&+AjVliGoiSC6(ReQP9S`-ikqVA&#o{_Uk+S?aPCX?lQLg5v+P*N>+l|fuT{D*>J z#~cSvsyz-?tFz*np>)WO%H}_1elfO-d332-?`7)|3#5WudKkK&OrlO!^?Ug!p2pyH zfvM>}IIu1_@qszl>Y2KL!e%FRbzNEO3S)45e0h$h>?DUa2fO9PyDu9F=T*#&BeR7~ zxhU7|B27ZCE>%KuY^|)+p8DPP^jOiFo$c-tYIpf&EdT9|v_YOZ4UzyU?DS_q&>s(d z=MdL@S>eT)Qs+d@YUjoRxmGF1lsyj*Yp|si%C&9#Qh^{84kQ0J>Q#f()3|Oa#w`c%cJr!_KL&RcK{KzU{w=y)!4Nv6#CAG9 z5ArjygfEjx7bQus0Dq+0Q~cE0(UWxi3YaT4n`|#Sy!$+;yBLl> z`xFYjqz4R%nzH$r!WPF1sxo!B^03rrhr|8US&#N}IlfuLZXh-;eV2}s*9>iVm;d}# zyOn`f4<|c5?p{=y%81b#Ol>DO=J?gx@j?Symxv8cdd=9!%0eEI6`PxzDZ#>HD%u)h zCJqkmgEi{v2H^TddqPovN=dUfJ%iZtKC0u61Ak;!n!I@l^~rqM@~{GpIeC6J7fq+r z){;^|dLD}mw7 + + + + + + + + diff --git a/static/limeClipper/rotate.svg b/static/limeClipper/rotate.svg new file mode 100644 index 0000000..0143706 --- /dev/null +++ b/static/limeClipper/rotate.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/static/login-index/apple.png b/static/login-index/apple.png new file mode 100644 index 0000000000000000000000000000000000000000..04a3579cbd143037161da2779fb695f731818d81 GIT binary patch literal 18205 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 F_aCE?rxXAH literal 0 HcmV?d00001 diff --git a/static/tabbar/grid_active.png b/static/tabbar/grid_active.png new file mode 100644 index 0000000000000000000000000000000000000000..81df4c78f5562c303606bd515b928c741e803edf GIT binary patch literal 4757 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 Kig3#=`hNgn^lJ+M literal 0 HcmV?d00001 diff --git a/static/tabbar/list.png b/static/tabbar/list.png new file mode 100644 index 0000000000000000000000000000000000000000..7ddda5e31e93694190259410b1e7eec2dcb35186 GIT binary patch literal 6130 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 literal 0 HcmV?d00001 diff --git a/static/tabbar/list_active.png b/static/tabbar/list_active.png new file mode 100644 index 0000000000000000000000000000000000000000..195b149b2fbf4f224d390507954635c9fa2a83e7 GIT binary patch literal 5264 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 HoN)gEtlp8l literal 0 HcmV?d00001 diff --git a/static/tabbar/me.png b/static/tabbar/me.png new file mode 100644 index 0000000000000000000000000000000000000000..1bff84a5047cc57dcb03d76e476bd1fda120c43c GIT binary patch literal 6771 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 literal 0 HcmV?d00001 diff --git a/static/uni-center/grey.png b/static/uni-center/grey.png new file mode 100644 index 0000000000000000000000000000000000000000..2aae15a5d0b9eea8451df6395d53775bcc676f78 GIT binary patch literal 6669 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 X90PicW&Nka#%63_pyE9VD36^-bCtH_z$)Z zw)eD;vkgKYj@PW#)$;nRB`uzh9~cN-Db@~MwKs2Y?goUB_c|% zs3>!3ppE+nh_W^06omu^@*(!?bMvqvR2m03yr<8}57;LMxQT_dpml2mRp19qg`fbD z=L`ZI0yiSNi2=8mv8C}Lj9Jm$Ve{BXRJpwhW*?JCa3;~1ibeEAMS zGTCCREYATiF)Rf%g)khPoa3;fUp~lG(UDeE7?nwEI5^XgKpf+c6a!A=E*mp6w5(7J zzKcQ}oFE4roW47(C-0uD;#u7F>SWkBpjg5owiXtdmk$ryP&70*b+oXynTLmmmivc? zwjnaI6bK(L)lW18aWAtu_TIXwM|WaEm0rRT5J;JR{~;>tFdicyU`yHQ8oC&&tBHXi zj@*{k5GydZha>cn8UaB<+5>6{dJT2~Sb=TroFwUwn%d|AcGi;g214q*>QFiGD?24G z82E*khAznKH3(=;FD(U-@DO`s;0SiH1b8?)IKjm{B{nhKR@8F z5B;Mwn6-_Vj=aKOvL5dw>0i0HK*e}?+}+)|-37QIFk2ozAP~sI%g@8l&-F;b1^0Av zvGm|_f;0SsgFF}xg4sb`>>y5n-yAKiAg(Tw^pBeUX@Vp4Z?;bGzuffbFdh#}C=VYu z?{Aa-AhZVkO$T*_Is75q8pH#306T)6T;Pwie1FqIUqM_T@K=z3!}{;-{~+Mewd(4B z%lJ=qadiA!1l;Ah+oKtOIpja3hU}>wc+21?=!6>kY-M=CIz4JFIR7?&Awse8ObRiIje_%)JA6^8=$^FI(5WuQ# z39@tg?OL|qhWvF3EN|%omZazB<>%w#72@LK)#VfVjV3-J4qj0)Ufw@R)gjh)HlF`a zQeiQEpcp^ze;|E?vbCj)<$p_T4HC0~z#J_f17YWAX$$6oI@!_#{tmC09K-i36|>gr<3PH-1XClFX!UXuRNLT)=dYcXDswUq!!#EMH) zR20Z1XblqJvIGG^Tvh@iq9QgR8xWXR@E_;pAt2Y^fc$;_&)l?zfF61LQz@X8pa`#s zjVPD3wJ0x_C7&RO3n=)Qg4R|bQC=WWRKOAp`Uf{nnB8M}uypuWuD`Xie&i@9D8OrN z#Ruf#6A~5T5)=Rexqx6R0WJ{OMu?9eEXdE#|H$VzBE%lqJgQ{*I|n7{`Tklo{EbWp z*S{781$iGoJbb_Z|3&cfza#kPzMhlaV+{D|0l!i8r`umV7Pv?E3JVDd2>rFLW#<8Q zFqF4@^bh<`Rrr99+Wc|mkG+@wvL`CU|GR|z({KOq!L!Hcz#pLs|EomFTH5|~v zKcB#3atQMaa|!Z+z+6CUUJ))UenF6xB@hS(0Uz=8U+6C&#wYlXV(@>g|6c+?uPmKx z!H->)hyLGM1cErZfnom=6{sc5^0AqMVQ@)$8yLh9U%V%`-@pL4 zKmh-&KL1vZH5g|1FTVR%DSxW~_`fLczm1fTfPf`GFYqxwyrO(uf+9d+E}*C|h)al1 zRLBM_EWj_o|NpN@{kInV&+7EQ!`}bai2g5Q?~m&8XKCR1->b%-_ws)NMBz8&{umF$ z{(teI9^2Y&Zz~t+nCQmzM0|u)>qRcQ<2uZSm6TQpY=r>$nfsXWJJK z5dZ5-!4&PCE@w_tEXBmF#b*eG> zJT#-bXQqn%3J=L#cT4|8`b}X)KKz(k#T`K4`aCQ6FsJinU6ThokQWY zMr{P2nB)M=H8CWJW3M7(zMtRST=7LNvk9L263|0g%FTChJkkUCcE2ypDp(v?ga?Xo zxISd$4j*xh)fbbI{p>n3ljGR1~Q?aD^F1p3%0@x=9FV8L

RwWF?>* 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 as{aS#+1wiZzndHY0000;|E?-7wm^cp3i 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 AQ~&?~ literal 0 HcmV?d00001 diff --git a/static/uni-quick-login/univerify.png b/static/uni-quick-login/univerify.png new file mode 100644 index 0000000000000000000000000000000000000000..00909a84976e151fdfcb7b000e52ed46dc2c6e33 GIT binary patch literal 6793 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 literal 0 HcmV?d00001 diff --git a/static/uni-quick-login/user.png b/static/uni-quick-login/user.png new file mode 100644 index 0000000000000000000000000000000000000000..57153d320f4eec453d8cb7ffe042a95e81347622 GIT binary patch literal 6307 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 V#vVy5&u2jZ3U5_qYNgFT{68%Qxi0_! literal 0 HcmV?d00001 diff --git a/static/uni-sign-in/background.png b/static/uni-sign-in/background.png new file mode 100644 index 0000000000000000000000000000000000000000..2d7ba0999a55dea2553a7319e93c8904b51d7810 GIT binary patch literal 93495 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

L2lMOJ~ z1grFbnA8(9bK(Znp0;!A8NBqyu(3b2SnQ}w*|8c)I%%ZrXDk8I#j)oxW()*bzYdWU zAhzq-6{KvyB${37v=eliIFkb@h=Da3wykRB1`8i8)ECwnaUkPydLfdrt!cn2yN>l< zsUb*AcLSK)T@W)vn=Pu)NU>Jg0xnt{ePzg!?bZoss7)b}n)yhw@G-;QqYKE98n84b zt@IHmxrcV~6#&K#UKjaPSwkcDjTjIG60x#)DVQzkrmw=F;oa$(8S9qOWaR{sX#{6& zz?eJ(@PUTS9k6v3{4Gow{Fr9}-`mex@>&l*Gac|{HIZq65VQD^2?(Zn=_$uqZDKKy zpMRFY7!F89av{y0RjFN9+d-Xa8iWWFqCnZJ5EDJw^|<&Nd9G6+W(wiQK&%5O0a^TS z+0NuSfYd&Nxzcxxg~PYPSB?U35t+l}?Mju)*d=iJ2&B+FMd5G?%YtMJhS{lufuZZ? zpB^q-&4pT6XTlAJ6&h( zQcFq{n%j5YoCgxUBz#Eb{wm|IoPsgsGd2VbiWwWpnT$XbhQ z_=4e@Tw8jLSnUZ}o#?es?O2_I0BFfLKU~=`teY0bYC7baWQWDhHN{RW$noAgdD?1$ z10E?|fpRz^!x?Pm>dWR%I6h!bZsooy2&!^p@<40Kco}AoF=qFKtt*c)XQI2z(bXb> zrC2~`nL7G0%!CuevK1B+#>}Br%EA$^Fv~FB!6qkJ)9RN;XI*oRILg@M+`=nrp}B>h z@;?)144iFRfU+_rMMHIv&M_v_$mkyEW4rzT^b&aUOV=(l0PnsF;L!j%)*V-6CbA%h z$lY9+nl3U2jjz=NuyrF?uwnJ#>HwUrFC$hLfmQxI5U2oj?3e-{o5Mdh03b;xQ@ew@M5M}EyKm-lUr` ziDwhUd!5^Sw}U_8kY2$jvmm(rd&xztb5I3c5a&w9Y7mAeQ)E#NeV|y$NeEOduXl=w zs3e`=V4gk0YAvQ46c&*MW6^Pa?WwK<%8QOD zI6rb$?A}G^CkDo@HDew7gu(A+-XfaN=JH@#Ns;{c=Vhk_?b# zJg9oBXr8qg0Ob~35<}kwIGf|6A?F~;A};)VvYBB~UM333%7?6MLQKA=5K55IUJrFm z|I9IC8!*;(DKeQQIpak^Oj8Ho^8Q$j834TgCE(?om4SKpJpk{BpBWmf2*o^^I*t?; z3(IvOIf&6PWs@2{ha_oGH31-*nmAqK7SEm6*)RwZKa1e&1Hf3D0oiX@7YAonbA%L4 z2Gzt5`vv&l zw5ez(1+eOlns!1HW^K!cIZ0?^L;6?IOUvSL3wZu=hs z#)iI6(zlDf#-Y1XY|9Gj*%v;%x)ka!J;D`?eFmI=*vIi4T8xXXpQH$vjksDf!$P4Q z%aVq|OAMOPggj{j-76q7+EQghS)7l1B2$6Vo>=qaP61dm)<6LrvjXag)H-3l#z_g##;u_k81x=!6NYZmXwkhPssbXIu?1W-Neze2r(sQ< zB??UiV2wO%Y?RMC;W?{0b6p!VX0?2iRD_>-W(=nG>CocDVvs-Y_81_W(VaqGfw&I!nn>_a z#}4yA6QE4_yiI+}!kmM<)-kx2+wM$YmbVsxi$%pt4iuJRd+-LACs0)2~A?rx@0pqV6Y?w5p3Y45Pt7h|T14@jL8gcLbduVH=k^+Kzz|d7yQEjbW$3 zgw3O%KruhrNMGQE<}$aFfuB3Tu&Sk^pdW%c0|$mda)g;&I2jX1+14?jt0e&BK&y)Y zlYL_Y$j;Zg)H53E7&AZr>`9aZF!tAfC#|$&GXMt*Q<8^Edc_bBa~n`EzjpbLX5x`m z^85~X{1YtZn6wBQYdic?H`-)vBC_Su-N+gqu+sg~{oa5Q#&}VNsCIo~-HATQT4ZO< zfK7xqVr7ocOQ-FD=%}fTo`a!6(WQ;KU;@bix zSA&={unx z^TqgesPfF2KX#o+Q9ocnBO9#QG!uvgFdM)!Irrt>Ch4%L8iHMNyzE9zzoqQ>hWtLJhiZH=Iklm~RP zs+G#rMfubEyomXF5zOCiD(SlX#seL=IXt%r!XOhF(@B$&J)5@#$?JP64-%tlWvGrNH;_FC*bujUVzvV)ic;hy!#^}iA@&Z;wi;Wsuoi-z*d8H z8E9m+OBBcjXqMzrH-DMcNRB0${|#X+DM&5XW+^U@BEjAH$Kk)l_JI`-4=9lo?BeNk zVZm-@tqaSiIz*EPBQwzUWTXggzmSj5Y}iB_Nap<{I>YJ)N$$*w8@ zA*9f|tK(TL1PohtR8moFlC;>s$^cU7$ep*U?;*ybjuAV$I94D|gWI7U>?4GcsNVA( zVkglkIjlo6OwHlMgCPWaR&6*s`w@C>#-hTy4VViQLMqw<`5+r8?+8C*m_G^#`_h@Y zek?F{&8B2^Np;egR(Q3suE--d==$OBzI$EaM19WK{5AXQ+jT)Y zFD_bV7Ble1KK9HeHqoU7!LF(IF-fz^q9O7~!GK#5_D_trX*5^u)~R(P1IG<3QJx-v z)2j;zldkt$N(`GCMMG((_~KnBw8$b35Sg#04FJ?I6fm;w1Wby1%O1+g=0*WN zwAn(`ExCKz`EN}?NE#;5hK~~kpmrZR>9vlfNgdTFEr8gvEE{FIs0lV31(3zFz0Ns~ z+ua;Z@%X7D!twwZ`|H1*I{)wv15GM>Vc^5Gc@W}s^$>eeI{Rvxe8-Ia34pg`_n*gd zHxK0{3L3jweNb<%Qgbn8zZ%vr8*G=1HLGssj18Qa$zo+Ox&ZXfK1l(nqjIN<;$WSD zrxzDFG9K0iXvaRC1<(@cciT|1zcCy9wK>W2;{}k>G182g$%H67xf~0|#-m^gTp%&Z z&U_gUF`(Z;Jg%DE`Y4v=o;*MWK%uYW!Yn})@+)?a0Kr2ZVT=XCx{z17i~*T&=BK1% zvF*g00?SDUB0P%H3=2U%xJV=2%!aW_|tpPl5cbSS<_|_HBbbPg<_qe)*DMObgW(eu#-Q0GEO?_iLhl%j;ufv3fPf*+}SQ92$*R=nY3(R z(%I=+5B7?5d7Z`BNgZZR$4ZL(umx&o0FS`=%%vfK)MhgCWhP6&PwuG-`-8AO)qGX3 zRv|Vf(Y3rc#-5V}l6 zntzfE*q2(uoPsf|u^=fJohmc6>_mCEQVoo_Fk)L@Cfb=Hs*@|TaDE4zzfbDTj0crF zX6n&Z*(AUhrAH%ARi-Ju&)ANU0S?uXZU3AhYWlFICNNgB>pZG8WvP&sL5&lu3(5kj zIx!(92LPNd0PNuf_zrrm}@N7)A#1H+y2w*&N{;BX%WA*kUw78u!&2%VzfJ% zF}N&5ee#IZM~iw@&~w>HNEU^$NSGVcw)1tfh|+1ap~<5&q%|@bivenjtVwN{vrU-9 z(A2pypvO!&32X?YTX`k=Ix#94v-l!Gjmt*m$;1Id_Pd-qDQOD zRFv6kY!}&N_gH}^XDDe_j%ooDFm*d*-e88q_534_0XJn}8>&b?8`pX??;~AzU1n%k z!ySdOZ~!RSoJve*lQBrbfu40`)|8V4|DK*|u}(uQj4i$qS{!H&aOM2X;O`n_ZJ1ff-|9t4bm%28)$rgv7VKkFS4upEF4p z!4MHiL6|WBj~nAx)s5&DxonudXjn2hP+1&{r`8ZS02mchq$J8oZQSx5f*nx@THXEW z?leVrqL`prjpDM%hyvHV((M~q(~+9J+Z`YQS__aX6JGcX^R?o9lm*;e=pW@O@%WfEi#?Y6BEZmjWCQpy2gU#et>Mw*?z9tOY# z-{`af+R&Ylb%QoB(4E$^9bx(?E5?jy!406dYs^S;%e6z)Nv&)wB?G=NlBl={p*C63 z1M%84!jg7mAbWKm_$to;O37b;})vI3sUc4qU16!mS>qNf$?PtK-A71|c0>DFnhFy=X zEv5j(Oror-khzR!Hc}++1CFel^hoAn5}ByeeNt?Hb3*q8zeW=Vvpb(B7VO0;C8ns6 ztY(J*sYR^G5)6P>N%st#uX1Fv*F)!@hDml%Ac%H~5gP@VdJ5mNZ^<+x$8(ir2y*rO0oT&2n<`-&({LaLO+k@7ic zy!rW`9*q4@emnQYw9QU7Q_qjtB|4W0=f#^##1WW$!q}bYi@4u@2E6_B`Iip~yf{JT zNVWmYM}SKDEqr@@OHpNqh-`p&a}J4Pgi~wan;d*9c8o3N)ex2e)CQYs;^yiN_VDUi zibMtY;0-K`=UVc7$=es7>ovM9Am=^vmhUf3CX2UP5EhiM8Ogv}aFbKpDNQaIGgM_A z&^7n3W5ihe&xcIj-FTSDTV3%t1l!;?Beo+jNy!C#pf;_uH2|vvHEj`jVJNr2RoXJJ zc%9JCt?Pn4oZVPKk%{rD=Fhk1|HL*~LD(La@2kAyj)2VWm&v`yC~L=TVhR|0>YBWH z7ZxxW4p@tOmoYz=pM$Xq#;yhYT`PXafF0@H3tZ~MUj33_aKyxvEP7}2=iQAFD?5vq zAplP!%Zyp4VNY)k%#ECk2Lcp2b65$+SZMD-VCL9C>Z?Tx{&&Ek8axhs?_85e;37jl zyxMf=lBl8Oz*&}Ram3#q<5I+3V~&bcnD4G^SaNo2xt&F_Wku>zMj1tKM@eHup>$aWS#k~*FNzSZX5W2ylyFYbI)h3rP#Ksbh1YO|I>b|0Y z>SVR6O+b4Ep>lKa^FMPiMr+0j6BZpP3r-EI`RH^4-h7=577$w0L^o`A@b5l-X<&a;G+qmK%{c}_H#C?lJe;RIK8=m zusVrvUw=Mhsc~YPlBf3)+lt-wK8vw)1#{>3m;cm-QqK7aA&a?SZxJ#KR%}d6e>c+0^^ z-cBMT?4Zg4RYu0+$RNQ8xo+Kn0BeNtgK*bZxK!Gmu;Vv_Q37SQjuc|!W?z8~2m`rK zE_X9n#9e&F|V!gR6?RT(UDu(MN>F<+A{ivQDvnh z6Z!S$tk_2YV>T*CEWq1=trqkOMxF}x35vH-PGT@Tz5~Aa{)V+6#;Ye>0GVn;pv;ph ztjzm>7)vEkPqsBg#oDgyo`fU|c*6TXEhe9A0vXn)-Lv^%#l5WNDR6bif3vqm$;wt$ zKwB*%>JB$WChq35&M8=+^Yfb?-(HxosD=GRHd>67nL%wmmE_& zqB1wG5knOapE)>=^YgVkpUE@!2%N7>m`f!6@j+=F4pYe6P|0taGmcm zxWXbCN@3*vb3d_<2*&=W*KSHG8!>8LJm3rrY+rvmU5s7hq}O5@Fit4rOyKiBxz^w) zR%r8=S~mug5x2-VtPZY#pca5ky~i5RP#15ubICz!!dbY5w;)Pb7u?g#L0{L&l{`HIyYZ=?TmbUcikA2u0{~*AJEZbsB&2fJAerwXo`-q z(G?bqLO|Y+W~!RAD$6OV#XI?;wq(3AxiD8i9fpml0Jd%t*t{7-Cv$nrSVDqW?3s6C zbQ3d-)xv=NY%>g15PNVCm8_{Gd+Q7ZrT|^IJ_RWh44Zt(wv)H>I#W#SZ46w&*lo-v zVXt>77U-YtX};;t|J=ijl|`YpvK$Fy4N8>7HaO`?5pH zi`UqRJZ$NHTTkX72eYjF5bvtP%2EHdOP;Ni{1 z8!U+Yy8JnBSa<+UNiLY3V8DigN{Pdosxu| zIX?oQe-AunM;QJEfES@>kdbk%nfRKT>k%+fRvz0IX2YVzJ>&Y#fW~dS**K-+wQ`n`C!6S)kFmVYd0+!14_E1k{Ec={WyzJ_^(a7DkqXx z1&+#5KN`d(2^y6gkiIcXn$Rj%kK}05&SSL;4%iyME~K0?ngf8<<***cIK4;KdP zDIXFWqtsY|3Nt=j7}A}O1*lv7c{|~rS75&b!B>;k$9JGUZlN|YW^z^G>FQQ7$M$8% zAM0zC6s2U!BX$#Y(Q5M!hjU?T-cPI`RGe5016ZYHgE=8rle9ZFtR2Efu0{R4>|Fmb zIQK5uq0EsfBCi~<9*-%PfWYp1@av!DSy%v@>bg`}kM}Tm;_qFTFY|F*Sl9ZorK|UJ z!cvZtMshxCD0~mg`PBtbKTa@q^%)ByhgGi?%7E2wt}+?Ed;`4rWZfA^_RH)Bfs2jB z+aIip?NW$OqFNqq1GU*HDF)$?tSTlT$r6zXJ!8f!wL3qQdbi4;fkagibJ=JzBPTv! zJGS!X2ez#P!|M3GWI4wAI)!|b{rt=~7HVUEehWN)abaXxz;+VPsU+db3vdyDN)^g3 zFjw}Q_IpX`?1?s}59*JY!#Ijb@gAmai)DQ6kz%HZ(9+o{f(b;PpVg6&fawT6;hP96;A;B?El}rDl?J0L9#z@Bl9RI%v0={!4v>=RJ4zE9<6J@M*(6#GZN;}0)BxWa$xna16$Y6`Q+=@ z_mT*T-TRDv#&%-(^u~&HF72^W=Mm3RWtc#2unA|%dNsfA3~?t?XN~3AF<{ju0p#CU zYvrg#unmfuz1vf)FrY(5z) zCejUV_7zW?#9SLhqyd5f(e5=r2VNhO8T*}F4dY^6pwhH4D-iPHlS}@jihMxyU}yW% z_iz9I?Y&E{Wm|ft^^Uc7Tuz>I@}73uPP<%X6%^Q21(_+3N|XRgwCN$y;SV4|3(*1z zi5?vU;txP!s@$WCUYMUPkV9Xtphz_O1(Cq4 zX5nfhI@P2>Z0Dcy0p0EQc9)Cz+8qp8GZWXk5@XTvtWo5z8V|3UbZDCMs9s6ej}KkC z`~0qJ>-?g1pvLwK>BizkZhnJ-jc#~-b`GDNrbuRNx@da|QZY`{x~}GuyudQkZ5kQp zCf-o9f@`!cZJ9(6eK67ZpjIff9dtE`PPOMw8Jwd-Q`F{lwZA)xbSjAMWp!pXDE4nR zGHwGh^Zx8P8%%2mo&D7t@1{4_Mr7@p-?PuI;|=N8nnZNzqP%S}_RVS?^PSjoH^uBz zy#GZ#Z?AP`vh9U3T38-`ycAAvT62!=yoZb6a&_W%#q-kK7^p-%l|*EPL8W9tpAld# zY#fR_<5T8tY%|8c*-Kg%;h#jnjtf9DV#pq&pe`)3;|NVvBN4g3IS=ZSl}T&1Hm}a7 z;T>tV_$g`5D9!|Ua#62?s8;tiV=q$9-QgM$&=!TKa}DcYmp)4Os88X9by0&(wP*l( zS|T*z!8GII9*R-Lh5X(9AegS=%z%sfNRuW6_!heZdl|SHk#&4ywKaZ{9oti91i5lc zj1okw!}nawW#4z*xUV~79n_m3h*P3osc z3%1#i*eKuRNS18kk8Q%ag_~>Z)ir#b1H(&KvnFFiU6QW7)<>|6!c0lKepMXJSTA5R z2eb?}aZ=N5r@=hMK){3z1@3sZQ6m@4Np`?pZPV7so;i-SCLUxat(Pb_9{1}%^5N)xp7>vLRBNZbN*M{1|NLXVID+Ti| zUFvGq1WbBv#CjBA&j*0E-L=IG;Vwy(@c0_{vDvH@W2^LLb;G)k=hBnqPrb@N<19jE zHv-jfE@ndgHTtY41pFv%qRHo&6%Lz*WG6pcC!x*~r<*2(1nTTbANa8o64Bedm`p7eF;a2>PiP2hPvUig)1s(6h&tD-#+Js78BU$20Ic&qg3QMUSqS5?ILW*n2GjuK48j?G*7GmsxNr1ZD(X~O{E=yL3eX|Ukev-Z8J!1 z&-XN<63h-QuD(t|(Y=WXxrbPd7ZkcriAmEb+#a;j)c2-7V%3P$70(yPSb|(8#2j_LR!O*th%Y4dVw_*{($tieVw zJ$@$rHG9gGi6~eZyl*#$aHIx`vHOXXhHXSp5P2$-0>!1?%(1Z@!!oI9H`v`|bi(^X z5*f=x`*1jS)Qvk5S((~v1XYo5z-T9hg#LutXPN}h5MZB`7#nnRg;6r&TOvt!KPO$> zSv}Smb=E<8p*=aSo=Q(X_BXTc8e6^2&H(nnrEX?+%?PJ#L5y~eS!QFV6LH!MHi@u| zLMd+0c)258-diGHyF2n-WitL*?ZR76M-#1Y$Tf<7$hcG2v1nW$tfMd3_0xEUVE1{n zVqt^et{X1J4JC5huk$(|nOR?l?z5PR2n^~AyMkd8RSR4AlexIIHY!-`6%15QSDd6q zPqn|_O{~$WICc@eO%a+Pb5Y!Ax@KW-txTdZS2j9WlW|z)dK#XuQ6av3xq=-)A6Zz` zK6*Mb;I$d))rAlBP6mN!a8eKlXl0Q=2Kd_ zrN=pu;kx5wOjFCnA?sA(K9E;>KzGt1DyLD^}kp(4YKJSq@&q|xR1)%wzt~lNn-DiuDtI>)^>u3-r zjyf9K`_ZWGr3M{_nfR(Entz6C{wOMD11BXISRRSNK}1CpgWc_PfXQO~sVHA|IhOHs z1UE-A9q1msxGvi>Iu~c`9D?rM_F4;#hNE#uO~z>#L~!>}YBKWMbOPH%MPCZ>^`=F} z{?VbEQf|~v;(OHHv{#RNZuHt-WWmkZi9B}^Jo!+%zMc?bt1nhF0_S*9-$|lb(Y;|m zF{HaFPD_n$+8SK^GtU1tTiB2F26qqX$2&_Mm@BAn(;GFp+1F~i^|FHEK#+y=G2N)G zD(xRHDeR*Fs~Z%otuGoxf@)3IyrB>)iTDiG13QvxCa@EB;$4*m_}0#9?uwJ96VMbDvT4y~!H*G#YfWNe%q7tfBUdSa)?$QQ`Vzq^|^-fV9Xx zirN|v!=WgP8VTBRi9o1zzq8E0SN9;-pwzyG?3$g~gp|6dv35$!KB_|tI)t;$;b

68P{C-Oy@rOO?l^Tddcl|# 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 literal 0 HcmV?d00001 diff --git a/static/uni.ttf b/static/uni.ttf new file mode 100644 index 0000000000000000000000000000000000000000..60a1968d08cc6056c70b5402b2effac43c6f96a3 GIT binary patch literal 26164 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 literal 0 HcmV?d00001 diff --git a/store/index.js b/store/index.js new file mode 100644 index 0000000..0282d14 --- /dev/null +++ b/store/index.js @@ -0,0 +1,24 @@ +import user from '@/store/modules/user.js' + +// #ifndef VUE3 +import Vue from 'vue' +import Vuex from 'vuex' +Vue.use(Vuex) +const store = new Vuex.Store({ + modules: { + user + }, + strict: true +}) +// #endif + +// #ifdef VUE3 +import {createStore} from 'vuex' +const store = createStore({ + modules: { + user + } +}) +// #endif + +export default store \ No newline at end of file diff --git a/store/modules/user.js b/store/modules/user.js new file mode 100644 index 0000000..22d12ba --- /dev/null +++ b/store/modules/user.js @@ -0,0 +1,48 @@ +// 上次启动时的用户信息 +let userInfoHistory = uni.getStorageSync('userInfo') || {}; +let state = { + //是否已经登录 + hasLogin: Boolean(Object.keys(userInfoHistory).length), + //用户信息 + info: userInfoHistory + }, + getters = { + info(state) { + return state.info; + }, + hasLogin(state){ + return state.hasLogin; + } + }, + mutations = { + login(state, info) { //登录成功后的操作 + //原有的结合传来的参数 + let _info = state.info; + state.info = Object.assign({}, _info, info); + //设置为已经登录 + state.hasLogin = true; + console.log('state.info',state.info); + //存储最新的用户数据到本地持久化存储 + uni.setStorageSync('userInfo', state.info); + uni.setStorageSync('uni_id_token', state.info.token) + uni.setStorageSync('uni_id_token_expired', state.info.tokenExpired) + + }, + logout(state) { + state.info = {}; + state.hasLogin = false; + uni.setStorageSync('userInfo', {}); + uni.setStorageSync('uni_id_token', ''); + uni.setStorageSync('uni_id_token_expired', 0) + } + }, + actions = { + + } +export default { + namespaced: true, + state, + getters, + mutations, + actions +} \ No newline at end of file diff --git a/uni-starter.config.js b/uni-starter.config.js new file mode 100644 index 0000000..2b2fd22 --- /dev/null +++ b/uni-starter.config.js @@ -0,0 +1,92 @@ +//这是应用的配置页面,App.vue挂载到getApp().globalData.config +export default { + "h5": { + "url": "https://uni-starter.dcloud.net.cn", // 前端网页托管的域名 + // 在h5端全局悬浮引导用户下载app的功能 更多自定义要求在/common/openApp.js中修改 + // "openApp": { //如不需要本功能直接移除本节点即可 + // //点击悬浮下载栏后打开的网页链接 + // "openUrl": '/#/pages/ucenter/invite/invite', + // //左侧显示的应用名称 + // "appname": 'uni-starter', + // //应用的图标 + // "logo": './static/logo.png', + // } + }, + "mp": { + "weixin": { + //微信小程序原始id,微信小程序分享时 + "id": "gh_33446d7f7a26" + } + }, + "router": { + /* + 名词解释:“强制登录页” + 在打开定义的需强制登录的页面之前会自动检查(前端校验)uni_id_token的值是否有效, + 如果无效会自动跳转到登录页面 + 两种模式: + 1.needLogin:黑名单模式。枚举游客不可访问的页面。 + 2.visitor:白名单模式。枚举游客可访问的页面。 + * 注意:黑名单与白名单模式二选一 + */ + // "needLogin" : [ + // {pattern:/^\/pages\/list.*/}, //支持正则表达式 + // "/uni_modules/uni-news-favorite/pages/uni-news-favorite/list", + // "/uni_modules/uni-feedback/pages/uni-feedback/add" + // ], + "visitor" : [ + "/",//注意入口页必须直接写 "/" + {"pattern":/^\/pages\/list.*/}, //支持正则表达式 + {"pattern":/^\/pages\/ucenter\/login-page.*/}, + "/pages/common/webview/webview", + "/pages/grid/grid", + "/pages/ucenter/ucenter", + "/pages/ucenter/guestbook/guestbook", + "/pages/ucenter/about/about", + "/pages/ucenter/settings/settings" + ], + /* + login:配置登录类型与优先级 + 未列举到的,或设备环境不支持的选项,将被隐藏。如果你需要在不同平台有不同的配置,直接用条件编译即可 + 根据数组的第0项,决定登录方式的第一优先级。 + */ + "login": ["weixin","univerify","username", "smsCode", "apple"] + }, + //关于应用 + "about": { + //应用名称 + "appName": "uni-starter", + //应用logo + "logo": "/static/logo.png", + //公司名称 + "company": "北京xx网络技术有限公司", + //口号 + "slogan": "云端一体应用快速开发模版", + //政策协议 + "agreements": [{ + "title": "用户服务协议", //如果开启了多语言国际化,本配置将失效。请在 lang/en.js 和 lang/zh-Hans.js中配置 + "url": "请填写用户服务协议链接" //对应的网络链接 + }, + { + "title": "隐私政策", //如果开启了多语言国际化,本配置将失效。请在 lang/en.js 和 lang/zh-Hans.js中配置 + "url": "请填写隐私政策链接" //对应的网络链接 + } + ], + //应用的链接,用于分享到第三方平台和生成关于我们页的二维码 + "download": "", + //version + "version":"1.0.0" //用于非app端显示,app端自动获取 + }, + "download":{ //用于生成二合一下载页面 + "ios":"https://itunes.apple.com/cn/app/hello-uni-app/id1417078253?mt=8", + "android":"https://vkceyugu.cdn.bspapp.com/VKCEYUGU-97fca9f2-41f6-449f-a35e-3f135d4c3875/6d754387-a6c3-48ed-8ad2-e8f39b40fc01.apk" + }, + //用于打开应用市场评分界面 + "marketId":{ + "ios":"id1417078253", + "android":"123456" + }, + //配置多语言国际化。i18n为英文单词 internationalization的首末字符i和n,18为中间的字符数 是“国际化”的简称 + "i18n":{ + "enable":false //默认关闭,国际化。如果你想使用国际化相关功能,请改为true + } +} \ No newline at end of file diff --git a/uni.scss b/uni.scss new file mode 100644 index 0000000..45ae737 --- /dev/null +++ b/uni.scss @@ -0,0 +1,76 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + * + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$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:24rpx; +$uni-font-size-base:28rpx; +$uni-font-size-lg:32rpx; + +/* 图片尺寸 */ +$uni-img-size-sm:40rpx; +$uni-img-size-base:52rpx; +$uni-img-size-lg:80rpx; + +/* Border Radius */ +$uni-border-radius-sm: 4rpx; +$uni-border-radius-base: 6rpx; +$uni-border-radius-lg: 12rpx; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 10px; +$uni-spacing-row-base: 20rpx; +$uni-spacing-row-lg: 30rpx; + +/* 垂直间距 */ +$uni-spacing-col-sm: 8rpx; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 24rpx; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:40rpx; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:36rpx; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:30rpx; \ No newline at end of file diff --git a/uniCloud-aliyun/cloudfunctions/uni-analyse-searchhot/index.js b/uniCloud-aliyun/cloudfunctions/uni-analyse-searchhot/index.js new file mode 100644 index 0000000..1f16dc2 --- /dev/null +++ b/uniCloud-aliyun/cloudfunctions/uni-analyse-searchhot/index.js @@ -0,0 +1,49 @@ +'use strict'; +exports.main = async (event, context) => { + /** + * 根据搜索记录,设定时间间隔来归纳出热搜数据并存储在热搜表中 + */ + const SEARCHHOT = 'opendb-search-hot'; // 热搜数据库名称 + const SEARCHLOG = 'opendb-search-log'; // 搜索记录数据库名称 + const SEARCHLOG_timeZone = 604800000; // 归纳搜索记录时间间隔,毫秒数,默认为最近7天 + const SEARCHHOT_size = 10; // 热搜条数 + + const DB = uniCloud.database(); + const DBCmd = DB.command; + const $ = DB.command.aggregate; + const SEARCHHOT_db = DB.collection(SEARCHHOT); + const SEARCHLOG_db = DB.collection(SEARCHLOG); + const timeEnd = Date.now() - SEARCHLOG_timeZone; + + let { + data: searchHotData + } = await SEARCHLOG_db + .aggregate() + .match({ + create_date: DBCmd.gt(timeEnd) + }) + .group({ + _id: { + 'content': '$content', + }, + count: $.sum(1) + }) + .replaceRoot({ + newRoot: $.mergeObjects(['$_id', '$$ROOT']) + }) + .project({ + _id: false + }) + .sort({ + count: -1 + }) + .end(); + + let now = Date.now(); + searchHotData.map(item => { + item.create_date = now; + return item; + }).slice(0, SEARCHHOT_size); + // searchHotData = searchHotData.sort((a, b) => b.count - a.count).slice(0, SEARCHHOT_size); + return searchHotData.length ? await SEARCHHOT_db.add(searchHotData) : '' +}; diff --git a/uniCloud-aliyun/cloudfunctions/uni-analyse-searchhot/package.json b/uniCloud-aliyun/cloudfunctions/uni-analyse-searchhot/package.json new file mode 100644 index 0000000..6005add --- /dev/null +++ b/uniCloud-aliyun/cloudfunctions/uni-analyse-searchhot/package.json @@ -0,0 +1,14 @@ +{ + "name": "uni-analyse-searchhot", + "version": "1.0.0", + "description": "定时归纳热搜", + "main": "index.js", + "dependencies": {}, + "cloudfunction-config": { + "triggers": [{ + "name": "analyse-searchHot", + "type": "timer", + "config": "0 0 */2 * * * *" + }] + } +} diff --git "a/uniCloud-aliyun/database/JQL\346\225\260\346\215\256\345\272\223\347\256\241\347\220\206.jql" "b/uniCloud-aliyun/database/JQL\346\225\260\346\215\256\345\272\223\347\256\241\347\220\206.jql" new file mode 100644 index 0000000..4903b47 --- /dev/null +++ "b/uniCloud-aliyun/database/JQL\346\225\260\346\215\256\345\272\223\347\256\241\347\220\206.jql" @@ -0,0 +1,12 @@ +// 本文件用于,使用JQL语法操作项目关联的uniCloud空间的数据库,方便开发调试和远程数据库管理 +// 编写clientDB的js API(也支持常规js语法,比如var),可以对云数据库进行增删改查操作。不支持uniCloud-db组件写法 +// 可以全部运行,也可以选中部分代码运行。点击工具栏上的运行按钮或者按下【F5】键运行代码 +// 如果文档中存在多条JQL语句,只有最后一条语句生效 +// 如果混写了普通js,最后一条语句需是数据库操作语句 +// 此处代码运行不受DB Schema的权限控制,移植代码到实际业务中注意在schema中配好permission +// 不支持clientDB的action +// 数据库查询有最大返回条数限制,详见:https://uniapp.dcloud.net.cn/uniCloud/cf-database?id=limit +// 详细JQL语法,请参考 https://uniapp.dcloud.net.cn/uniCloud/clientdb?id=jsquery + +// 下面示例查询uni-id-users表的所有数据 +db.collection('guestbook, uni-id-users').where('user_id._id=="610a6daa506cc7000100c07f"').get(); diff --git "a/uniCloud-aliyun/database/JQL\346\237\245\350\257\242.jql" "b/uniCloud-aliyun/database/JQL\346\237\245\350\257\242.jql" new file mode 100644 index 0000000..680e898 --- /dev/null +++ "b/uniCloud-aliyun/database/JQL\346\237\245\350\257\242.jql" @@ -0,0 +1,9 @@ +// 本查询文件用于,使用JQL语法查询项目关联的uniCloud空间的数据库,方便开发调试 +// 选中查询代码,点击工具栏上的运行按钮或者【F5】运行查询语句 +// 如果没有选中代码,直接运行,则会执行整个文档的JQL语句。但如果文档中存在多条JQL语句,只有最后一条语句生效 +// 本文档支持简单js,但不支持clientDB的action +// 数据库查询有最大返回条数限制,详见:https://uniapp.dcloud.net.cn/uniCloud/cf-database?id=limit +// 详细JQL语法,请参考 https://uniapp.dcloud.net.cn/uniCloud/clientdb?id=jsquery + +// 下面示例查询uni-id-users表的所有数据 +db.collection('uni-id-users').get(); diff --git a/uniCloud-aliyun/database/db_init.json b/uniCloud-aliyun/database/db_init.json new file mode 100644 index 0000000..6c7d689 --- /dev/null +++ b/uniCloud-aliyun/database/db_init.json @@ -0,0 +1,157 @@ +// 在本文件中可配置云数据库初始化,数据格式见:https://uniapp.dcloud.io/uniCloud/cf-database?id=db_init +// 编写完毕后对本文件点右键,可按配置规则创建表和添加数据 +{ + "opendb-verify-codes": { + "data": [] + }, + "uni-id-roles": { + "data": [] + }, + "uni-id-permissions": { + "data": [] + }, + "uni-id-log": { + "data": [] + }, + "opendb-admin-menus": { + "data": [{ + "menu_id": "system_management", + "name": "系统管理", + "icon": "uni-icons-gear", + "url": "", + "sort": 1000, + "parent_id": "", + "permission": [], + "enable": true, + "create_date": 1602662469396 + }, { + "menu_id": "system_user", + "name": "用户管理", + "icon": "uni-icons-person", + "url": "/pages/system/user/list", + "sort": 1010, + "parent_id": "system_management", + "permission": [], + "enable": true, + "create_date": 1602662469398 + }, { + "menu_id": "system_role", + "name": "角色管理", + "icon": "uni-icons-personadd", + "url": "/pages/system/role/list", + "sort": 1020, + "parent_id": "system_management", + "permission": [], + "enable": true, + "create_date": 1602662469397 + }, { + "menu_id": "system_permission", + "name": "权限管理", + "icon": "uni-icons-locked", + "url": "/pages/system/permission/list", + "sort": 1030, + "parent_id": "system_management", + "permission": [], + "enable": true, + "create_date": 1602662469396 + }, { + "menu_id": "system_menu", + "name": "菜单管理", + "icon": "uni-icons-settings", + "url": "/pages/system/menu/list", + "sort": 1040, + "parent_id": "system_management", + "permission": [], + "enable": true, + "create_date": 1602662469396 + }] + }, + "opendb-news-articles": { + "data": [{ + "title": "阿里小程序IDE官方内嵌uni-app,为开发者提供多端开发服务", + "excerpt": "阿里小程序IDE官方内嵌uni-app,为开发者提供多端开发服务", + "content": "

随着微信、阿里、百度、头条、QQ纷纷推出小程序,开发者的开发维护成本持续上升,负担过重。这点已经成为共识,现在连小程序平台厂商也充分意识到了。

\n

阿里小程序团队,为了减轻开发者的负担,在官方的小程序开发者工具中整合了多端框架。

\n

经过阿里团队仔细评估,uni-app 在产品完成度、跨平台支持度、开发者社区、可持续发展等多方面优势明显,最终选定 uni-app内置于阿里小程序开发工具中,为开发者提供多端开发解决方案。

\n

经过之前1个月的公测,10月10日,阿里小程序正式发布0.70版开发者工具,通过 uni-app 实现多端开发,成为本次版本更新的亮点功能!

\n

如下图,在阿里小程序工具左侧主导航选择 uni-app,创建项目,即可开发。

\n
\n


阿里小程序开发工具更新说明详见:https://docs.alipay.com/mini/ide/0.70-stable

\n

 

\n

集成uni-app,这对于阿里团队而言,并不是一个容易做出的决定。毕竟 uni-app 是一个三方产品,要经过复杂的评审流程。

\n

这一方面突显出阿里团队以开发者需求为本的优秀价值观,另一方面也证明 uni-app的产品确实过硬。

\n

很多开发者都有多端需求,但又没有足够精力去了解、评估 uni-app,而处于观望态度。现在大家可以更放心的使用 uni-app 了,它没有让阿里失望,也不会让你失望。

\n

自从uni-app推出以来,DCloud也取得了高速的发展,目前拥有370万开发者,框架运行在4.6亿手机用户设备上,月活达到1.35亿(仅包括部分接入DCloud统计平台的数据)。并且数据仍在高速增长中,在市场占有率上处于遥遥领先的位置。

\n

本次阿里小程序工具集成 uni-app,会让 uni-app 继续快速爆发,取得更大的成功。

\n

后续DCloud还将深化与阿里的合作,在serverless等领域给开发者提供更多优质服务。

\n

使用多端框架开发各端应用,是多赢的模式。开发者减轻了负担,获得了更多新流量。而小程序平台厂商,也能保证自己平台上的各种应用可以被及时的更新。

\n

DCloud欢迎更多小程序平台厂商,与我们一起合作,为开发者、平台、用户的多赢而努力。

\n

进一步了解uni-app,详见:https://uniapp.dcloud.io

\n

欢迎扫码关注DCloud公众号,转发消息到朋友圈。

", + "avatar": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-aliyun-gacrhzeynhss7c6d04/249516a0-3941-11eb-899d-733ae62bed2f.jpg", + "type": 0, + "user_id": "123", + "comment_count": 0, + "like_count": 0, + "comment_status": 0, + "article_status": 1, + "publish_date": 1616092287006, + "last_modify_date": 1616092303031, + "create_time": "2021-03-19T08:25:06.109Z" + }] + }, + "opendb-app-versions": { + "data": [{ + "is_silently": false, + "is_mandatory": false, + "appid": "__UNI__03B096E", + "name": "uni-starter", + "title": "新增升级中心", + "contents": "新增升级中心", + "platform": [ + "Android" + ], + "version": "1.0.1", + "url": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-3469aac7-a663-4c5d-8ee8-94275f8c09ab/3128d010-01c5-4121-a1d6-f3f919944a23.apk", + "stable_publish": false, + "type": "native_app", + "create_date": 1616771628150 + }] + }, + "uni-id-users": { + "data": [{ + "_id": "123", + "username": "预置用户", + "nickname": "测试", + "avatar": "https://bjetxgzv.cdn.bspapp.com/VKCEYUGU-dc-site/d84c6de0-6080-11eb-bdc1-8bd33eb6adaa.png", + "mobile": "18888888888", + "mobile_confirmed": 1 + }] + }, + "opendb-banner": { + "data": [{ + "status": true, + "bannerfile": { + "name": "094a9dc0-50c0-11eb-b680-7980c8a877b8.jpg", + "extname": "jpg", + "fileType": "image", + "url": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e/b88a7e17-35f0-4d0d-bc32-93f8909baf03.jpg", + "size": 70880, + "image": { + "width": 500, + "height": 333, + "location": "blob:http://localhost:8081/a3bfaab4-7ee6-44d5-a171-dc8225d83598" + }, + "path": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e/b88a7e17-35f0-4d0d-bc32-93f8909baf03.jpg" + }, + "open_url": "https://www.dcloud.io/", + "title": "测试", + "sort": 1, + "category_id": "", + "description": "" + }, + { + "status": true, + "bannerfile": { + "name": "094a9dc0-50c0-11eb-b680-7980c8a877b8.jpg", + "extname": "jpg", + "fileType": "image", + "url": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e/9db94cb4-a5e0-4ed9-b356-b42a392b3112.jpg", + "size": 70880, + "image": { + "width": 500, + "height": 333, + "location": "blob:http://localhost:8081/1a6f718a-4012-476a-9172-590fef2cc518" + }, + "path": "https://vkceyugu.cdn.bspapp.com/VKCEYUGU-76ce2c5e-31c7-4d81-8fcf-ed1541ecbc6e/9db94cb4-a5e0-4ed9-b356-b42a392b3112.jpg" + }, + "open_url": "https://www.dcloud.io/", + "title": "", + "category_id": "", + "description": "" + }] + } +} \ No newline at end of file diff --git a/uniCloud-aliyun/database/default.jql b/uniCloud-aliyun/database/default.jql new file mode 100644 index 0000000..465356a --- /dev/null +++ b/uniCloud-aliyun/database/default.jql @@ -0,0 +1,12 @@ +// 本文件用于,使用JQL语法操作项目关联的uniCloud空间的数据库,方便开发调试和远程数据库管理 +// 编写clientDB的js API(也支持常规js语法,比如var),可以对云数据库进行增删改查操作。不支持uniCloud-db组件写法 +// 可以全部运行,也可以选中部分代码运行。点击工具栏上的运行按钮或者按下【F5】键运行代码 +// 如果文档中存在多条JQL语句,只有最后一条语句生效 +// 如果混写了普通js,最后一条语句需是数据库操作语句 +// 此处代码运行不受DB Schema的权限控制,移植代码到实际业务中注意在schema中配好permission +// 不支持clientDB的action +// 数据库查询有最大返回条数限制,详见:https://uniapp.dcloud.net.cn/uniCloud/cf-database?id=limit +// 详细JQL语法,请参考 https://uniapp.dcloud.net.cn/uniCloud/clientdb?id=jsquery + +// 下面示例查询uni-id-users表的所有数据 +db.collection('uni-id-users').get(); diff --git a/uniCloud-aliyun/database/guestbook.schema.json b/uniCloud-aliyun/database/guestbook.schema.json new file mode 100644 index 0000000..b4e9bb9 --- /dev/null +++ b/uniCloud-aliyun/database/guestbook.schema.json @@ -0,0 +1,57 @@ +// 文档教程: https://uniapp.dcloud.net.cn/uniCloud/schema +{ + "bsonType": "object", + "required": [], + "permission": { + "read": "doc.state || auth.uid == doc.user_id || 'AUDITOR' in auth.role", + "create": "auth.uid != null", + "update": "'AUDITOR' in auth.role", + "delete": "auth.uid == doc.user_id" + }, + "properties": { + "_id": { + "description": "ID,系统自动生成", + "permission":{ + "write":false + } + }, + "text": { + "bsonType": "string", + "permission":{ + "write":false + } + }, + "user_id": { + "forceDefaultValue": { + "$env": "uid" + }, + "foreignKey": "uni-id-users._id", + "permission":{ + "write":false + } + }, + "ip": { + "forceDefaultValue": { + "$env": "clientIP" + }, + "permission":{ + "write":false + } + }, + "create_time": { + "forceDefaultValue": { + "$env": "now" + }, + "permission":{ + "write":false + } + }, + "state": { + "bsonType": "bool", + "forceDefaultValue":false, + "permission":{ + "write":true + } + } + } +} \ No newline at end of file diff --git a/uniCloud-aliyun/database/opendb-admin-menus.schema.json b/uniCloud-aliyun/database/opendb-admin-menus.schema.json new file mode 100644 index 0000000..c6b9de5 --- /dev/null +++ b/uniCloud-aliyun/database/opendb-admin-menus.schema.json @@ -0,0 +1,56 @@ +{ + "bsonType": "object", + "required": ["name", "menu_id"], + "permission": { + "read": true + }, + "properties": { + "_id": { + "description": "存储文档 ID,系统自动生成" + }, + "menu_id": { + "bsonType": "string", + "description": "菜单项的ID,不可重复", + "trim": "both" + }, + "name": { + "bsonType": "string", + "description": "菜单名称", + "trim": "both" + }, + "icon": { + "bsonType": "string", + "description": "菜单图标", + "trim": "both" + }, + "url": { + "bsonType": "string", + "description": "菜单url", + "trim": "both" + }, + "sort": { + "bsonType": "int", + "description": "菜单序号(越大越靠后)" + }, + "parent_id": { + "bsonType": "string", + "description": "父级菜单Id", + "parentKey": "menu_id" + }, + "permission": { + "bsonType": "array", + "description": "菜单权限列表" + }, + "enable": { + "bsonType": "bool", + "description": "是否启用菜单,true启用、false禁用" + }, + "create_date": { + "bsonType": "timestamp", + "description": "菜单创建时间", + "forceDefaultValue": { + "$env": "now" + } + } + } +} diff --git a/uniCloud-aliyun/database/opendb-app-versions.schema.json b/uniCloud-aliyun/database/opendb-app-versions.schema.json new file mode 100644 index 0000000..a24c2d3 --- /dev/null +++ b/uniCloud-aliyun/database/opendb-app-versions.schema.json @@ -0,0 +1,124 @@ +{ + "bsonType": "object", + "required": ["appid", "platform", "version", "url", "contents", "type"], + "permission": { + "read": false, + "create": false, + "update": false, + "delete": false + }, + "properties": { + "_id": { + "description": "记录id,自动生成" + }, + "appid": { + "bsonType": "string", + "trim": "both", + "description": "应用的AppID", + "label": "AppID", + "componentForEdit": { + "name": "uni-easyinput", + "props": { + "disabled": true + } + } + }, + "name": { + "bsonType": "string", + "trim": "both", + "description": "应用名称", + "label": "应用名称", + "componentForEdit": { + "name": "uni-easyinput", + "props": { + "disabled": true + } + } + }, + "title": { + "bsonType": "string", + "description": "更新标题", + "label": "更新标题" + }, + "contents": { + "bsonType": "string", + "description": "更新内容", + "label": "更新内容", + "componentForEdit": { + "name": "textarea" + }, + "componentForShow": { + "name": "textarea", + "props": { + "disabled": true + } + } + }, + "platform": { + "bsonType": "array", + "enum": [{ + "value": "Android", + "text": "安卓" + }, { + "value": "iOS", + "text": "苹果" + }], + "description": "更新平台,Android || iOS || [Android, iOS]", + "label": "平台" + }, + "type": { + "bsonType": "string", + "enum": [{ + "value": "native_app", + "text": "原生App安装包" + }, { + "value": "wgt", + "text": "Wgt资源包" + }], + "description": "安装包类型,native_app || wgt", + "label": "安装包类型" + }, + "version": { + "bsonType": "string", + "description": "当前包版本号,必须大于当前线上发行版本号", + "label": "版本号" + }, + "min_uni_version": { + "bsonType": "string", + "description": "原生App最低版本", + "label": "原生App最低版本" + }, + "url": { + "bsonType": "string", + "description": "可下载安装包地址", + "label": "包地址" + }, + "stable_publish": { + "bsonType": "bool", + "description": "是否上线发行", + "label": "上线发行" + }, + "is_silently": { + "bsonType": "bool", + "description": "是否静默更新", + "label": "静默更新", + "defaultValue": false + }, + "is_mandatory": { + "bsonType": "bool", + "description": "是否强制更新", + "label": "强制更新", + "defaultValue": false + }, + "create_date": { + "bsonType": "timestamp", + "label": "上传时间", + "forceDefaultValue": { + "$env": "now" + }, + "componentForEdit": { + "name": "uni-dateformat" + } + } + } +} diff --git a/uniCloud-aliyun/database/opendb-banner.schema.json b/uniCloud-aliyun/database/opendb-banner.schema.json new file mode 100644 index 0000000..a027517 --- /dev/null +++ b/uniCloud-aliyun/database/opendb-banner.schema.json @@ -0,0 +1,54 @@ +{ + "bsonType": "object", + "required": ["bannerfile"], + "permission": { + "read": true + }, + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "bannerfile": { + "bsonType": "file", + "fileMediaType": "image", + "title": "图片文件", + "description": "图片文件信息,包括文件名、url等" + }, + "open_url": { + "bsonType": "string", + "description": "点击跳转目标地址。如果是web地址则使用内置web-view打开;如果是本地页面则跳转本地页面;如果是schema地址则打开本地的app", + "title": "点击目标地址", + "format": "url", + "pattern": "^(http:\/\/|https:\/\/|\/|.\/|@\/)\\S", + "trim": "both" + }, + "title": { + "bsonType": "string", + "description": "注意标题文字颜色和背景图靠色导致看不清的问题", + "maxLength": 20, + "title": "标题", + "trim": "both" + }, + "sort": { + "bsonType": "int", + "description": "数字越小,排序越前", + "title": "排序" + }, + "category_id": { + "bsonType": "string", + "description": "多个栏目的banner都存在一个表里时可用这个字段区分", + "title": "分类id" + }, + "status": { + "bsonType": "bool", + "defaultValue": true, + "title": "生效状态" + }, + "description": { + "bsonType": "string", + "description": "维护者自用描述", + "title": "备注", + "trim": "both" + } + } +} diff --git a/uniCloud-aliyun/database/opendb-department.schema.json b/uniCloud-aliyun/database/opendb-department.schema.json new file mode 100644 index 0000000..698239b --- /dev/null +++ b/uniCloud-aliyun/database/opendb-department.schema.json @@ -0,0 +1,47 @@ +{ + "bsonType": "object", + "required": ["name"], + "permission": { + "read": true, + "create": false, + "update": false, + "delete": false + }, + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "parent_id": { + "bsonType": "string", + "description": "父级部门ID", + "parentKey": "_id" + }, + "name": { + "bsonType": "string", + "description": "部门名称", + "title": "部门名称", + "trim": "both" + }, + "level": { + "bsonType": "int", + "description": "部门层级,为提升检索效率而作的冗余设计" + }, + "sort": { + "bsonType": "int", + "description": "部门在当前层级下的顺序,由小到大", + "title": "显示顺序" + }, + "manager_uid": { + "bsonType": "string", + "description": "部门主管的userid, 参考`uni-id-users` 表", + "foreignKey": "uni-id-users._id" + }, + "create_date": { + "bsonType": "timestamp", + "description": "部门创建时间", + "forceDefaultValue": { + "$env": "now" + } + } + } +} diff --git a/uniCloud-aliyun/database/opendb-mall-goods.schema.json b/uniCloud-aliyun/database/opendb-mall-goods.schema.json new file mode 100644 index 0000000..b3c24cc --- /dev/null +++ b/uniCloud-aliyun/database/opendb-mall-goods.schema.json @@ -0,0 +1,123 @@ +{ + "bsonType": "object", + "permission": { + "create": false, + "delete": false, + "read": "doc.is_on_sale == true", + "update": false + }, + "properties": { + "_id": { + "description": "存储文档 ID(商品 ID),系统自动生成" + }, + "add_date": { + "bsonType": "timestamp", + "defaultValue": { + "$env": "now" + }, + "description": "上架时间" + }, + "category_id": { + "bsonType": "string", + "description": "分类 id,参考`opendb-mall-categories`表", + "foreignKey": "opendb-mall-categories._id" + }, + "comment_count": { + "bsonType": "int", + "description": "累计评论数" + }, + "goods_banner_imgs": { + "bsonType": "array", + "description": "商品详情页的banner图地址" + }, + "goods_desc": { + "bsonType": "string", + "description": "商品详细描述", + "title": "详细描述", + "trim": "both" + }, + "goods_sn": { + "bsonType": "string", + "description": "商品的唯一货号", + "title": "货号", + "trim": "both" + }, + "goods_thumb": { + "bsonType": "string", + "description": "商品缩略图,用于在列表或搜索结果中预览显示", + "pattern": "^(http:\/\/|https:\/\/|\/|.\/|@\/)\\S", + "title": "缩略图地址", + "trim": "both" + }, + "is_alone_sale": { + "bsonType": "bool", + "description": "是否能单独销售;如果不能单独销售,则只能作为某商品的配件或者赠品销售" + }, + "is_best": { + "bsonType": "bool", + "description": "是否精品" + }, + "is_hot": { + "bsonType": "bool", + "description": "是否热销" + }, + "is_new": { + "bsonType": "bool", + "description": "是否新品", + "title": "是否新品" + }, + "is_on_sale": { + "bsonType": "bool", + "description": "是否上架销售", + "title": "是否上架" + }, + "is_real": { + "bsonType": "bool", + "description": "是否实物", + "title": "是否为实物" + }, + "keywords": { + "bsonType": "string", + "description": "商品关键字,为搜索引擎收录使用", + "title": "关键字", + "trim": "both" + }, + "last_modify_date": { + "bsonType": "timestamp", + "defaultValue": { + "$env": "now" + }, + "description": "最后修改时间" + }, + "month_sell_count": { + "bsonType": "int", + "description": "月销量" + }, + "name": { + "bsonType": "string", + "description": "商品名称", + "title": "名称", + "trim": "both" + }, + "remain_count": { + "bsonType": "int", + "description": "库存数量", + "title": "库存数量" + }, + "seller_note": { + "bsonType": "string", + "description": "商家备注,仅商家可见", + "permission": { + "read": false + }, + "trim": "both" + }, + "total_sell_count": { + "bsonType": "int", + "description": "总销量" + } + }, + "required": ["goods_sn", "name", "remain_count", "month_sell_count", "total_sell_count", "comment_count", "is_real", + "is_on_sale", "is_alone_sale", "is_best", "is_new", "is_hot" + ] +} diff --git a/uniCloud-aliyun/database/opendb-news-articles-detail.schema.json b/uniCloud-aliyun/database/opendb-news-articles-detail.schema.json new file mode 100644 index 0000000..325fc9a --- /dev/null +++ b/uniCloud-aliyun/database/opendb-news-articles-detail.schema.json @@ -0,0 +1,122 @@ +{ + "bsonType": "object", + "permission": { + "create": "auth.uid != null", + "delete": "doc.user_id == auth.uid", + "read": true, + "update": "doc.user_id == auth.uid" + }, + "properties": { + "_id": { + "description": "存储文档 ID(用户 ID),系统自动生成" + }, + "article_status": { + "bsonType": "int", + "description": "文章状态:0 草稿箱 1 已发布", + "maximum": 1, + "minimum": 0 + }, + "avatar": { + "bsonType": "string", + "description": "缩略图地址", + "label": "封面大图" + }, + "category_id": { + "bsonType": "string", + "description": "分类 id,参考`uni-news-categories`表" + }, + "comment_count": { + "bsonType": "int", + "description": "评论数量", + "permission": { + "write": false + } + }, + "comment_status": { + "bsonType": "int", + "description": "评论状态:0 关闭 1 开放", + "maximum": 1, + "minimum": 0 + }, + "content": { + "bsonType": "string", + "description": "文章内容", + "label": "文章内容" + }, + "excerpt": { + "bsonType": "string", + "description": "文章摘录", + "label": "摘要" + }, + "is_essence": { + "bsonType": "bool", + "description": "阅读加精", + "permission": { + "write": false + } + }, + "is_sticky": { + "bsonType": "bool", + "description": "是否置顶", + "permission": { + "write": false + } + }, + "last_comment_user_id": { + "bsonType": "string", + "description": "最后回复用户 id,参考`uni-id-users` 表" + }, + "last_modify_date": { + "bsonType": "timestamp", + "description": "最后修改时间" + }, + "last_modify_ip": { + "bsonType": "string", + "description": "最后修改时 IP 地址" + }, + "like_count": { + "bsonType": "int", + "description": "喜欢数、点赞数", + "permission": { + "write": false + } + }, + "mode": { + "bsonType": "number", + "description": "排版显示模式" + }, + "publish_date": { + "bsonType": "timestamp", + "defaultValue": { + "$env": "now" + }, + "description": "发表时间" + }, + "publish_ip": { + "bsonType": "string", + "description": "发表时 IP 地址", + "forceDefaultValue": { + "$env": "clientIP" + } + }, + "title": { + "bsonType": "string", + "description": "标题", + "label": "标题" + }, + "user_id": { + "bsonType": "string", + "description": "文章作者ID, 参考`uni-id-users` 表" + }, + "view_count": { + "bsonType": "int", + "description": "阅读数量", + "permission": { + "write": false + } + } + }, + "required": ["user_id", "title", "content", "article_status", "view_count", "like_count", "is_sticky", "is_essence", + "comment_status", "comment_count", "mode" + ] +} diff --git a/uniCloud-aliyun/database/opendb-news-articles.schema.json b/uniCloud-aliyun/database/opendb-news-articles.schema.json new file mode 100644 index 0000000..ee4f8cc --- /dev/null +++ b/uniCloud-aliyun/database/opendb-news-articles.schema.json @@ -0,0 +1,165 @@ +{ + "bsonType": "object", + "required": ["user_id", "title", "content"], + "permission": { + "read": "doc.uid == auth.uid && doc.article_status == 0 || doc.article_status == 1", + "create": "auth.uid != null", + "update": "doc.user_id == auth.uid", + "delete": "doc.user_id == auth.uid" + }, + "properties": { + "_id": { + "description": "存储文档 ID(用户 ID),系统自动生成" + }, + "user_id": { + "bsonType": "string", + "description": "文章作者ID, 参考`uni-id-users` 表", + "foreignKey": "uni-id-users._id", + "defaultValue": { + "$env": "uid" + } + }, + "category_id": { + "bsonType": "string", + "title": "分类", + "description": "分类 id,参考`uni-news-categories`表", + "foreignKey": "opendb-news-categories._id", + "enum": { + "collection": "opendb-news-categories", + "field": "name as text, _id as value" + } + }, + "title": { + "bsonType": "string", + "title": "标题", + "description": "标题", + "label": "标题", + "trim": "both" + }, + "content": { + "bsonType": "string", + "title": "文章内容", + "description": "文章内容", + "label": "文章内容", + "trim": "right" + }, + "excerpt": { + "bsonType": "string", + "title": "文章摘录", + "description": "文章摘录", + "label": "摘要", + "trim": "both" + }, + "article_status": { + "bsonType": "int", + "title": "文章状态", + "description": "文章状态:0 草稿箱 1 已发布", + "defaultValue": 0, + "enum": [{ + "value": 0, + "text": "草稿箱" + }, { + "value": 1, + "text": "已发布" + }] + }, + "view_count": { + "bsonType": "int", + "title": "阅读数量", + "description": "阅读数量", + "permission": { + "write": false + } + }, + "like_count": { + "bsonType": "int", + "description": "喜欢数、点赞数", + "permission": { + "write": false + } + }, + "is_sticky": { + "bsonType": "bool", + "title": "是否置顶", + "description": "是否置顶", + "permission": { + "write": false + } + }, + "is_essence": { + "bsonType": "bool", + "title": "阅读加精", + "description": "阅读加精", + "permission": { + "write": false + } + }, + "comment_status": { + "bsonType": "int", + "title": "开放评论", + "description": "评论状态:0 关闭 1 开放", + "enum": [{ + "value": 0, + "text": "关闭" + }, { + "value": 1, + "text": "开放" + }] + }, + "comment_count": { + "bsonType": "int", + "description": "评论数量", + "permission": { + "write": false + } + }, + "last_comment_user_id": { + "bsonType": "string", + "description": "最后回复用户 id,参考`uni-id-users` 表", + "foreignKey": "uni-id-users._id" + }, + "avatar": { + "bsonType": "string", + "title": "封面大图", + "description": "缩略图地址", + "label": "封面大图", + "trim": "both" + }, + "publish_date": { + "bsonType": "timestamp", + "title": "发表时间", + "description": "发表时间", + "defaultValue": { + "$env": "now" + } + }, + "publish_ip": { + "bsonType": "string", + "title": "发布文章时IP地址", + "description": "发表时 IP 地址", + "forceDefaultValue": { + "$env": "clientIP" + } + }, + "last_modify_date": { + "bsonType": "timestamp", + "title": "最后修改时间", + "description": "最后修改时间", + "defaultValue": { + "$env": "now" + } + }, + "last_modify_ip": { + "bsonType": "string", + "description": "最后修改时 IP 地址", + "forceDefaultValue": { + "$env": "clientIP" + } + }, + "mode": { + "bsonType": "number", + "title": "排版显示模式", + "description": "排版显示模式,如左图右文、上图下文等" + } + } +} diff --git a/uniCloud-aliyun/database/opendb-news-categories.schema.json b/uniCloud-aliyun/database/opendb-news-categories.schema.json new file mode 100644 index 0000000..976c8a8 --- /dev/null +++ b/uniCloud-aliyun/database/opendb-news-categories.schema.json @@ -0,0 +1,50 @@ +{ + "bsonType": "object", + "required": ["name"], + "permission": { + "read": true, + "create": false, + "update": false, + "delete": false + }, + "properties": { + "_id": { + "description": "存储文档 ID(文章 ID),系统自动生成" + }, + "name": { + "bsonType": "string", + "description": "类别名称", + "label": "名称", + "trim": "both" + }, + "description": { + "bsonType": "string", + "description": "类别描述", + "label": "描述", + "trim": "both" + }, + "icon": { + "bsonType": "string", + "description": "类别图标地址", + "label": "图标地址", + "pattern": "^(http:\/\/|https:\/\/|\/|.\/|@\/)\\S", + "trim": "both" + }, + "sort": { + "bsonType": "int", + "description": "类别显示顺序", + "label": "排序" + }, + "article_count": { + "bsonType": "int", + "description": "该类别下文章数量" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + } + } +} diff --git a/uniCloud-aliyun/database/opendb-news-comments.schema.json b/uniCloud-aliyun/database/opendb-news-comments.schema.json new file mode 100644 index 0000000..1c0b197 --- /dev/null +++ b/uniCloud-aliyun/database/opendb-news-comments.schema.json @@ -0,0 +1,68 @@ +{ + "bsonType": "object", + "required": ["article_id", "user_id", "comment_content", "like_count", "comment_type", "reply_user_id", + "reply_comment_id" + ], + "permission": { + "read": true, + "create": "auth.uid != null && get(`database.opendb-news-article.${doc.article_id}`).comment_status == 1", + "update": "doc.user_id == auth.uid", + "delete": "doc.user_id == auth.uid" + }, + "properties": { + "_id": { + "description": "存储文档 ID(文章 ID),系统自动生成" + }, + "article_id": { + "bsonType": "string", + "description": "文章ID,opendb-news-posts 表中的`_id`字段", + "foreignKey": "opendb-news-articles._id" + }, + "user_id": { + "bsonType": "string", + "description": "评论者ID,参考`uni-id-users` 表", + "forceDefaultValue": { + "$env": "uid" + }, + "foreignKey": "uni-id-users._id" + }, + "comment_content": { + "bsonType": "string", + "description": "评论内容", + "title": "评论内容", + "trim": "right" + }, + "like_count": { + "bsonType": "int", + "description": "评论喜欢数、点赞数" + }, + "comment_type": { + "bsonType": "int", + "description": "回复类型: 0 针对文章的回复 1 针对评论的回复" + }, + "reply_user_id": { + "bsonType": "string", + "description": "被回复的评论用户ID,comment_type为1时有效", + "foreignKey": "uni-id-users._id" + }, + "reply_comment_id": { + "bsonType": "string", + "description": "被回复的评论ID,comment_type为1时有效", + "foreignKey": "opendb-news-comments._id" + }, + "comment_date": { + "bsonType": "timestamp", + "description": "评论发表时间", + "forceDefaultValue": { + "$env": "now" + } + }, + "comment_ip": { + "bsonType": "string", + "description": "评论发表时 IP 地址", + "forceDefaultValue": { + "$env": "clientIP" + } + } + } +} diff --git a/uniCloud-aliyun/database/opendb-news-favorite.schema.json b/uniCloud-aliyun/database/opendb-news-favorite.schema.json new file mode 100644 index 0000000..a4034f1 --- /dev/null +++ b/uniCloud-aliyun/database/opendb-news-favorite.schema.json @@ -0,0 +1,46 @@ +{ + "bsonType": "object", + "required": ["user_id", "article_id"], + "permission": { + "read": "doc.user_id == auth.uid", + "create": "auth.uid != null", + "update": "doc.user_id == auth.uid", + "delete": "doc.user_id == auth.uid" + }, + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "article_id": { + "bsonType": "string", + "description": "文章id,参考opendb-news-articles表", + "foreignKey": "opendb-news-articles._id" + }, + "article_title": { + "bsonType": "string", + "description": "文章标题" + }, + "user_id": { + "bsonType": "string", + "description": "收藏者id,参考uni-id-users表", + "forceDefaultValue": { + "$env": "uid" + }, + "foreignKey": "uni-id-users._id" + }, + "create_date": { + "bsonType": "timestamp", + "description": "收藏时间", + "forceDefaultValue": { + "$env": "now" + } + }, + "update_date": { + "bsonType": "timestamp", + "description": "更新\/修改时间", + "forceDefaultValue": { + "$env": "now" + } + } + } +} diff --git a/uniCloud-aliyun/database/opendb-search-hot.schema.json b/uniCloud-aliyun/database/opendb-search-hot.schema.json new file mode 100644 index 0000000..1230be4 --- /dev/null +++ b/uniCloud-aliyun/database/opendb-search-hot.schema.json @@ -0,0 +1,27 @@ +{ + "bsonType": "object", + "permission": { + "create": false, + "delete": false, + "read": true, + "update": false + }, + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "content": { + "bsonType": "string", + "description": "搜索内容" + }, + "count": { + "bsonType": "long", + "description": "搜索次数" + }, + "create_date": { + "bsonType": "timestamp", + "description": "统计时间" + } + }, + "required": ["content", "count"] +} diff --git a/uniCloud-aliyun/database/opendb-search-log.schema.json b/uniCloud-aliyun/database/opendb-search-log.schema.json new file mode 100644 index 0000000..421ee8c --- /dev/null +++ b/uniCloud-aliyun/database/opendb-search-log.schema.json @@ -0,0 +1,31 @@ +{ + "bsonType": "object", + "permission": { + "create": true, + "delete": false, + "read": false, + "update": false + }, + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "content": { + "bsonType": "string", + "description": "搜索内容" + }, + "create_date": { + "bsonType": "timestamp", + "description": "统计时间" + }, + "device_id": { + "bsonType": "string", + "description": "设备id" + }, + "user_id": { + "bsonType": "string", + "description": "收藏者id,参考uni-id-users表" + } + }, + "required": ["content"] +} diff --git a/uniCloud-aliyun/database/opendb-verify-codes.schema.json b/uniCloud-aliyun/database/opendb-verify-codes.schema.json new file mode 100644 index 0000000..50c27c6 --- /dev/null +++ b/uniCloud-aliyun/database/opendb-verify-codes.schema.json @@ -0,0 +1,45 @@ +{ + "bsonType": "object", + "required": [], + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "mobile": { + "bsonType": "string", + "description": "手机号码" + }, + "email": { + "bsonType": "string", + "description": "邮箱" + }, + "device_uuid": { + "bsonType": "string", + "description": "设备UUID,常用于图片验证码" + }, + "code": { + "bsonType": "string", + "description": "验证码" + }, + "scene": { + "bsonType": "string", + "description": "使用验证码的场景,如:login, bind, unbind, pay" + }, + "state": { + "bsonType": "int", + "description": "验证状态:0 未验证、1 已验证、2 已作废" + }, + "ip": { + "bsonType": "string", + "description": "请求时客户端IP地址" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间" + }, + "expired_date": { + "bsonType": "timestamp", + "description": "过期时间" + } + } +} \ No newline at end of file diff --git a/uniCloud-aliyun/database/read-news-log.schema.json b/uniCloud-aliyun/database/read-news-log.schema.json new file mode 100644 index 0000000..1cbd342 --- /dev/null +++ b/uniCloud-aliyun/database/read-news-log.schema.json @@ -0,0 +1,35 @@ +{ + "bsonType": "object", + "required": ["user_id", "article_id"], + "permission": { + "read": "doc.user_id == auth.uid", + "create": "auth.uid != null", + "update": "doc.user_id == auth.uid" + //"delete": "doc.user_id == auth.uid" + }, + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "article_id": { + "bsonType": "string", + "description": "文章id,参考opendb-news-articles表", + "foreignKey": "opendb-news-articles._id" + }, + "user_id": { + "bsonType": "string", + "description": "收藏者id,参考uni-id-users表", + "forceDefaultValue": { + "$env": "uid" + }, + "foreignKey": "uni-id-users._id" + }, + "last_time": { //设计策略是多次看同一个文章只做一次记录,重复看的文章时间更新为当前时间 + "bsonType": "timestamp", + "description": "最后一次看的时间", + "defaultValue": { + "$env": "now" + } + } + } +} diff --git a/uniCloud-aliyun/database/uni-id-device.schema.json b/uniCloud-aliyun/database/uni-id-device.schema.json new file mode 100644 index 0000000..483a785 --- /dev/null +++ b/uniCloud-aliyun/database/uni-id-device.schema.json @@ -0,0 +1,64 @@ +{ + "bsonType": "object", + "required": ["user_id"], + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "user_id": { + "bsonType": "string", + "description": "用户id,参考uni-id-users表" + }, + "ua": { + "bsonType": "string", + "description": "userAgent" + }, + "uuid": { + "bsonType": "string", + "description": "设备唯一标识(需要加密存储)" + }, + "vendor": { + "bsonType": "string", + "description": "设备厂商" + }, + "push_clientid": { + "bsonType": "string", + "description": "推送设备客户端标识" + }, + "imei": { + "bsonType": "string", + "description": "国际移动设备识别码IMEI(International Mobile Equipment Identity)" + }, + "oaid": { + "bsonType": "string", + "description": "移动智能设备标识公共服务平台提供的匿名设备标识符(OAID)" + }, + "idfa": { + "bsonType": "string", + "description": "iOS平台配置应用使用广告标识(IDFA)" + }, + "model": { + "bsonType": "string", + "description": "设备型号" + }, + "platform": { + "bsonType": "string", + "description": "平台类型" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + }, + "last_active_date": { + "bsonType": "timestamp", + "description": "最后登录时间" + }, + "last_active_ip": { + "bsonType": "string", + "description": "最后登录IP" + } + } +} diff --git a/uniCloud-aliyun/database/uni-id-log.schema.json b/uniCloud-aliyun/database/uni-id-log.schema.json new file mode 100644 index 0000000..086432a --- /dev/null +++ b/uniCloud-aliyun/database/uni-id-log.schema.json @@ -0,0 +1,41 @@ +{ + "bsonType": "object", + "required": ["user_id"], + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "user_id": { + "bsonType": "string", + "description": "用户id,参考uni-id-users表" + }, + "ua": { + "bsonType": "string", + "description": "userAgent" + }, + "device_uuid": { + "bsonType": "string", + "description": "设备唯一标识(需要加密存储)" + }, + "type": { + "bsonType": "string", + "enum": ["login", "logout"], + "description": "登录类型" + }, + "state": { + "bsonType": "int", + "description": "结果:0 失败、1 成功" + }, + "ip": { + "bsonType": "string", + "description": "ip地址" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + } + } +} diff --git a/uniCloud-aliyun/database/uni-id-permissions.schema.json b/uniCloud-aliyun/database/uni-id-permissions.schema.json new file mode 100644 index 0000000..f0ddc99 --- /dev/null +++ b/uniCloud-aliyun/database/uni-id-permissions.schema.json @@ -0,0 +1,46 @@ +{ + "bsonType": "object", + "required": ["permission_id", "permission_name"], + "properties": { + "_id": { + "description": "存储文档 ID,系统自动生成" + }, + "permission_id": { + "bsonType": "string", + "description": "权限唯一标识,不可修改,不允许重复", + "label": "权限标识", + "trim": "both", + "title": "权限ID", + "component": { + "name": "input" + } + }, + "permission_name": { + "bsonType": "string", + "description": "权限名称", + "label": "权限名称", + "title": "权限名称", + "trim": "both", + "component": { + "name": "input" + } + }, + "comment": { + "bsonType": "string", + "description": "备注", + "label": "备注", + "title": "备注", + "trim": "both", + "component": { + "name": "textarea" + } + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + } + } +} diff --git a/uniCloud-aliyun/database/uni-id-roles.schema.json b/uniCloud-aliyun/database/uni-id-roles.schema.json new file mode 100644 index 0000000..9802441 --- /dev/null +++ b/uniCloud-aliyun/database/uni-id-roles.schema.json @@ -0,0 +1,46 @@ +{ + "bsonType": "object", + "required": ["role_id"], + "permission": { + "read": false, + "create": false, + "update": false, + "delete": false + }, + "properties": { + "_id": { + "description": "存储文档 ID,系统自动生成" + }, + "role_id": { + "title": "唯一ID", + "bsonType": "string", + "description": "角色唯一标识,不可修改,不允许重复", + "trim": "both" + }, + "role_name": { + "title": "名称", + "bsonType": "string", + "description": "角色名称", + "trim": "both" + }, + "permission": { + "title": "权限", + "bsonType": "array", + "foreignKey": "uni-id-permissions.permission_id", + "description": "角色拥有的权限列表" + }, + "comment": { + "title": "备注", + "bsonType": "string", + "description": "备注", + "trim": "both" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + } + } +} diff --git a/uniCloud-aliyun/database/uni-id-scores.schema.json b/uniCloud-aliyun/database/uni-id-scores.schema.json new file mode 100644 index 0000000..0e2a7f6 --- /dev/null +++ b/uniCloud-aliyun/database/uni-id-scores.schema.json @@ -0,0 +1,44 @@ +{ + "bsonType": "object", + "required": ["user_id", "score", "balance"], + "permission": { + "read": "doc.user_id == auth.uid", + "create": false, + "update": false, + "delete": false + }, + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "user_id": { + "bsonType": "string", + "description": "用户id,参考uni-id-users表" + }, + "score": { + "bsonType": "int", + "description": "本次变化的积分" + }, + "type": { + "bsonType": "int", + "enum": [1, 2], + "description": "积分类型 1:收入 2:支出" + }, + "balance": { + "bsonType": "int", + "description": "变化后的积分余额" + }, + "comment": { + "bsonType": "string", + "description": "备注,说明积分新增、消费的缘由", + "trim": "both" + }, + "create_date": { + "bsonType": "timestamp", + "description": "创建时间", + "forceDefaultValue": { + "$env": "now" + } + } + } +} diff --git a/uniCloud-aliyun/database/uni-id-users.schema.json b/uniCloud-aliyun/database/uni-id-users.schema.json new file mode 100644 index 0000000..a75e9cb --- /dev/null +++ b/uniCloud-aliyun/database/uni-id-users.schema.json @@ -0,0 +1,366 @@ +{ + "bsonType": "object", + "permission":{ + "read":true, + "update":"doc._id == auth.uid" + }, + "required": [], + "properties": { + "_id": { + "description": "存储文档 ID(用户 ID),系统自动生成" + }, + "username": { + "bsonType": "string", + "title": "用户名", + "description": "用户名,不允许重复", + "trim": "both", + "permission":{ + "write":false + } + }, + "password": { + "bsonType": "password", + "title": "密码", + "description": "密码,加密存储", + "trim": "both", + "permission":{ + "write":false + } + }, + "password_secret_version": { + "bsonType": "int", + "title": "passwordSecret", + "description": "密码使用的passwordSecret版本", + "permission":{ + "write":false + } + }, + "nickname": { + "bsonType": "string", + "title": "昵称", + "description": "用户昵称", + "trim": "both" + }, + "gender": { + "bsonType": "int", + "title": "性别", + "description": "用户性别:0 未知 1 男性 2 女性", + "defaultValue": 0, + "enum": [{ + "text": "未知", + "value": 0 + }, + { + "text": "男", + "value": 1 + }, + { + "text": "女", + "value": 2 + } + ] + }, + "status": { + "bsonType": "int", + "description": "用户状态:0 正常 1 禁用 2 审核中 3 审核拒绝", + "title": "用户状态", + "defaultValue": 0, + "permission":{ + "write":false + }, + "enum": [{ + "text": "正常", + "value": 0 + }, + { + "text": "禁用", + "value": 1 + }, + { + "text": "审核中", + "value": 2 + }, + { + "text": "审核拒绝", + "value": 3 + } + ] + }, + "mobile": { + "bsonType": "string", + "title": "手机号码", + "description": "手机号码", + "pattern": "^\\+?[0-9-]{3,20}$", + "trim": "both", + "permission":{ + "write":false + } + }, + "mobile_confirmed": { + "bsonType": "int", + "description": "手机号验证状态:0 未验证 1 已验证", + "title": "手机号验证状态", + "defaultValue": 0, + "enum": [{ + "text": "未验证", + "value": 0 + }, + { + "text": "已验证", + "value": 1 + } + ], + "permission":{ + "write":false + } + }, + "email": { + "bsonType": "string", + "format": "email", + "title": "邮箱", + "description": "邮箱地址", + "trim": "both", + "permission":{ + "write":false + } + }, + "email_confirmed": { + "bsonType": "int", + "description": "邮箱验证状态:0 未验证 1 已验证", + "title": "邮箱验证状态", + "defaultValue": 0, + "enum": [{ + "text": "未验证", + "value": 0 + }, + { + "text": "已验证", + "value": 1 + } + ], + "permission":{ + "write":false + } + }, + "avatar": { + "bsonType": "string", + "title": "头像地址", + "description": "头像地址", + "trim": "both" + }, + "avatar_file": { + "bsonType": "file", + "title": "头像文件", + "description": "用file类型方便使用uni-file-picker组件" + }, + "department_id": { + "bsonType": "array", + "description": "部门ID", + "permission":{ + "write":false + }, + "title": "部门", + "enumType": "tree", + "enum": { + "collection": "opendb-department", + "orderby": "name asc", + "field": "_id as value, name as text" + } + }, + "role": { + "bsonType": "array", + "title": "角色", + "description": "用户角色", + "enum": { + "collection": "uni-id-roles", + "field": "role_id as value, role_name as text" + }, + "foreignKey": "uni-id-roles.role_id", + "permission": { + "write": false + } + }, + "wx_unionid": { + "bsonType": "string", + "description": "微信unionid", + "permission":{ + "write":false + } + }, + "wx_openid": { + "bsonType": "object", + "description": "微信各个平台openid", + "properties": { + "app-plus": { + "bsonType": "string", + "description": "app平台微信openid" + }, + "mp-weixin": { + "bsonType": "string", + "description": "微信小程序平台openid" + } + }, + "permission":{ + "write":false + } + }, + "ali_openid": { + "bsonType": "string", + "description": "支付宝平台openid", + "permission":{ + "write":false + } + }, + "apple_openid": { + "bsonType": "string", + "description": "苹果登录openid", + "permission":{ + "write":false + } + }, + "comment": { + "bsonType": "string", + "title": "备注", + "description": "备注", + "trim": "both", + "permission":{ + "write":false + } + }, + "realname_auth": { + "bsonType": "object", + "description": "实名认证信息", + "required": [ + "type", + "auth_status" + ], + "permission":{ + "write":false + }, + "properties": { + "type": { + "bsonType": "int", + "minimum": 0, + "maximum": 1, + "description": "用户类型:0 个人用户 1 企业用户" + }, + "auth_status": { + "bsonType": "int", + "minimum": 0, + "maximum": 3, + "description": "认证状态:0 未认证 1 等待认证 2 认证通过 3 认证失败" + }, + "auth_date": { + "bsonType": "timestamp", + "description": "认证通过时间" + }, + "real_name": { + "bsonType": "string", + "description": "真实姓名/企业名称" + }, + "identity": { + "bsonType": "string", + "description": "身份证号码/营业执照号码" + }, + "id_card_front": { + "bsonType": "string", + "description": "身份证正面照 URL" + }, + "id_card_back": { + "bsonType": "string", + "description": "身份证反面照 URL" + }, + "in_hand": { + "bsonType": "string", + "description": "手持身份证照片 URL" + }, + "license": { + "bsonType": "string", + "description": "营业执照 URL" + }, + "contact_person": { + "bsonType": "string", + "description": "联系人姓名" + }, + "contact_mobile": { + "bsonType": "string", + "description": "联系人手机号码" + }, + "contact_email": { + "bsonType": "string", + "description": "联系人邮箱" + } + } + }, + "score": { + "bsonType": "int", + "description": "用户积分,积分变更记录可参考:uni-id-scores表定义", + "permission":{ + "write":false + } + }, + "register_date": { + "bsonType": "timestamp", + "description": "注册时间", + "forceDefaultValue": { + "$env": "now" + }, + "permission":{ + "write":false + } + }, + "register_ip": { + "bsonType": "string", + "description": "注册时 IP 地址", + "forceDefaultValue": { + "$env": "clientIP" + }, + "permission":{ + "write":false + } + }, + "last_login_date": { + "bsonType": "timestamp", + "description": "最后登录时间", + "permission":{ + "write":false + } + }, + "last_login_ip": { + "bsonType": "string", + "description": "最后登录时 IP 地址", + "permission":{ + "write":false + } + }, + "token": { + "bsonType": "array", + "description": "用户token", + "permission":{ + "write":false + } + }, + "inviter_uid": { + "bsonType": "array", + "description": "用户全部上级邀请者", + "trim": "both", + "permission":{ + "write":false + } + }, + "invite_time": { + "bsonType": "timestamp", + "description": "受邀时间", + "permission":{ + "write":false + } + }, + "my_invite_code": { + "bsonType": "string", + "description": "用户自身邀请码", + "permission":{ + "write":false + } + } + } +} diff --git a/uni_modules.config.json b/uni_modules.config.json new file mode 100644 index 0000000..ebabb75 --- /dev/null +++ b/uni_modules.config.json @@ -0,0 +1,6 @@ +{ + "scripts": { + "preupload": "node uni_modules_tools/main.js change", + "postupload": "node uni_modules_tools/main.js recovery" + } +} \ No newline at end of file diff --git a/uni_modules/json-gps/changelog.md b/uni_modules/json-gps/changelog.md new file mode 100644 index 0000000..d9847e2 --- /dev/null +++ b/uni_modules/json-gps/changelog.md @@ -0,0 +1,2 @@ +## 1.0.0(2021-07-12) +第一版发布 diff --git a/uni_modules/json-gps/js_sdk/gps.js b/uni_modules/json-gps/js_sdk/gps.js new file mode 100644 index 0000000..34bd0e8 --- /dev/null +++ b/uni_modules/json-gps/js_sdk/gps.js @@ -0,0 +1,133 @@ +// #ifdef APP-PLUS +import permision from "./wa-permission/permission.js" +// #endif +class Gps { + constructor(arg) { + this.lock = false //锁防止重复请求 + } + async getLocation(param = { + type: 'wgs84' + }) { + return new Promise(async (callback) => { + if (this.lock) { + // console.log('已锁,已有另一个请求正在执行。无需重复请求'); + callback(false); + return false + } + this.lock = true //加锁防止重复的请求 + uni.getLocation({ + ...param, + success: res => { + this.lock = false //成功后解锁 + //console.log(res); + callback(res) + }, + fail: async (err) => { + uni.showToast({ + title: '定位获取失败', + icon: 'none' + }); + console.error(err) + callback(false) + + // #ifdef APP-PLUS + await this.checkGpsIsOpen() + // #endif + + + // #ifdef MP-WEIXIN + if (err.errMsg == 'getLocation:fail auth deny') { + uni.showModal({ + content: '应用无定位权限', + confirmText: '前往设置', + complete: (e) => { + if (e.confirm) { + uni.openSetting({ + success(res) { + console.log(res.authSetting) + } + }); + } + this.lock = false //解锁让回到界面重新获取 + } + }); + } + if (err.errMsg == 'getLocation:fail:ERROR_NOCELL&WIFI_LOCATIONSWITCHOFF') { + uni.showModal({ + content: '未开启定位权限,请前往手机系统设置中开启', + showCancel: false, + confirmText:"知道了" + }); + } + // #endif + } + }); + }) + } + // #ifdef APP-PLUS + async checkGpsIsOpen() { + this.lock = true //加锁防止重复的请求 + console.log('检查定位设置开启问题', permision.checkSystemEnableLocation()); + if (!permision.checkSystemEnableLocation()) { + plus.nativeUI.confirm("手机定位权限没有开启,是否去开启?", (e) => { + this.lock = false + if (e.index == 0) { + if (uni.getSystemInfoSync().platform == "ios") { + plus.runtime.openURL("app-settings://"); + } else { + var main = plus.android.runtimeMainActivity(); //获取activity + var Intent = plus.android.importClass('android.content.Intent'); + var Settings = plus.android.importClass('android.provider.Settings'); + var intent = new Intent(Settings + .ACTION_LOCATION_SOURCE_SETTINGS); //可设置表中所有Action字段 + main.startActivity(intent); + } + } else { + uni.showToast({ + title: '设备无定位权限', + icon: 'none' + }); + callback(false) + } + }, { + "buttons": ["去设置", "不开启"], + "verticalAlign": "center" + }); + return false + } + let checkAppGpsRes = await this.checkAppGps() + console.log(checkAppGpsRes, 'checkAppGpsRes'); + if (!checkAppGpsRes) { + plus.nativeUI.confirm("应用定位权限没有开启,是否去开启?", (e) => { + this.lock = false + if (e.index == 0) { + permision.gotoAppPermissionSetting() + callback(false) + } else { + uni.showToast({ + title: '应用无定位权限', + icon: 'none' + }); + return false + } + }, { + "buttons": ["去设置", "不开启"], + "verticalAlign": "center" + }); + } else { + this.lock = false + } + } + async checkAppGps() { + if (uni.getSystemInfoSync().platform == "ios" && !permision.judgeIosPermission("location")) { + return false + } + if (uni.getSystemInfoSync().platform != "ios" && await permision.requestAndroidPermission( + "android.permission.ACCESS_FINE_LOCATION") != 1) { + return false + } + return true + } + // #endif +} +export default Gps diff --git a/uni_modules/json-gps/js_sdk/wa-permission/permission.js b/uni_modules/json-gps/js_sdk/wa-permission/permission.js new file mode 100644 index 0000000..501cab4 --- /dev/null +++ b/uni_modules/json-gps/js_sdk/wa-permission/permission.js @@ -0,0 +1,272 @@ +/** + * 本模块封装了Android、iOS的应用权限判断、打开应用权限设置界面、以及位置系统服务是否开启 + */ + +var isIos +// #ifdef APP-PLUS +isIos = (plus.os.name == "iOS") +// #endif + +// 判断推送权限是否开启 +function judgeIosPermissionPush() { + var result = false; + var UIApplication = plus.ios.import("UIApplication"); + var app = UIApplication.sharedApplication(); + var enabledTypes = 0; + if (app.currentUserNotificationSettings) { + var settings = app.currentUserNotificationSettings(); + enabledTypes = settings.plusGetAttribute("types"); + console.log("enabledTypes1:" + enabledTypes); + if (enabledTypes == 0) { + console.log("推送权限没有开启"); + } else { + result = true; + console.log("已经开启推送功能!") + } + plus.ios.deleteObject(settings); + } else { + enabledTypes = app.enabledRemoteNotificationTypes(); + if (enabledTypes == 0) { + console.log("推送权限没有开启!"); + } else { + result = true; + console.log("已经开启推送功能!") + } + console.log("enabledTypes2:" + enabledTypes); + } + plus.ios.deleteObject(app); + plus.ios.deleteObject(UIApplication); + return result; +} + +// 判断定位权限是否开启 +function judgeIosPermissionLocation() { + var result = false; + var cllocationManger = plus.ios.import("CLLocationManager"); + var status = cllocationManger.authorizationStatus(); + result = (status != 2) + console.log("定位权限开启:" + result); + // 以下代码判断了手机设备的定位是否关闭,推荐另行使用方法 checkSystemEnableLocation + /* var enable = cllocationManger.locationServicesEnabled(); + var status = cllocationManger.authorizationStatus(); + console.log("enable:" + enable); + console.log("status:" + status); + if (enable && status != 2) { + result = true; + console.log("手机定位服务已开启且已授予定位权限"); + } else { + console.log("手机系统的定位没有打开或未给予定位权限"); + } */ + plus.ios.deleteObject(cllocationManger); + return result; +} + +// 判断麦克风权限是否开启 +function judgeIosPermissionRecord() { + var result = false; + var avaudiosession = plus.ios.import("AVAudioSession"); + var avaudio = avaudiosession.sharedInstance(); + var permissionStatus = avaudio.recordPermission(); + console.log("permissionStatus:" + permissionStatus); + if (permissionStatus == 1684369017 || permissionStatus == 1970168948) { + console.log("麦克风权限没有开启"); + } else { + result = true; + console.log("麦克风权限已经开启"); + } + plus.ios.deleteObject(avaudiosession); + return result; +} + +// 判断相机权限是否开启 +function judgeIosPermissionCamera() { + var result = false; + var AVCaptureDevice = plus.ios.import("AVCaptureDevice"); + var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide'); + console.log("authStatus:" + authStatus); + if (authStatus == 3) { + result = true; + console.log("相机权限已经开启"); + } else { + console.log("相机权限没有开启"); + } + plus.ios.deleteObject(AVCaptureDevice); + return result; +} + +// 判断相册权限是否开启 +function judgeIosPermissionPhotoLibrary() { + var result = false; + var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary"); + var authStatus = PHPhotoLibrary.authorizationStatus(); + console.log("authStatus:" + authStatus); + if (authStatus == 3) { + result = true; + console.log("相册权限已经开启"); + } else { + console.log("相册权限没有开启"); + } + plus.ios.deleteObject(PHPhotoLibrary); + return result; +} + +// 判断通讯录权限是否开启 +function judgeIosPermissionContact() { + var result = false; + var CNContactStore = plus.ios.import("CNContactStore"); + var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0); + if (cnAuthStatus == 3) { + result = true; + console.log("通讯录权限已经开启"); + } else { + console.log("通讯录权限没有开启"); + } + plus.ios.deleteObject(CNContactStore); + return result; +} + +// 判断日历权限是否开启 +function judgeIosPermissionCalendar() { + var result = false; + var EKEventStore = plus.ios.import("EKEventStore"); + var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0); + if (ekAuthStatus == 3) { + result = true; + console.log("日历权限已经开启"); + } else { + console.log("日历权限没有开启"); + } + plus.ios.deleteObject(EKEventStore); + return result; +} + +// 判断备忘录权限是否开启 +function judgeIosPermissionMemo() { + var result = false; + var EKEventStore = plus.ios.import("EKEventStore"); + var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1); + if (ekAuthStatus == 3) { + result = true; + console.log("备忘录权限已经开启"); + } else { + console.log("备忘录权限没有开启"); + } + plus.ios.deleteObject(EKEventStore); + return result; +} + +// Android权限查询 +function requestAndroidPermission(permissionID) { + return new Promise((resolve, reject) => { + plus.android.requestPermissions( + [permissionID], // 理论上支持多个权限同时查询,但实际上本函数封装只处理了一个权限的情况。有需要的可自行扩展封装 + function(resultObj) { + var result = 0; + for (var i = 0; i < resultObj.granted.length; i++) { + var grantedPermission = resultObj.granted[i]; + console.log('已获取的权限:' + grantedPermission); + result = 1 + } + for (var i = 0; i < resultObj.deniedPresent.length; i++) { + var deniedPresentPermission = resultObj.deniedPresent[i]; + console.log('拒绝本次申请的权限:' + deniedPresentPermission); + result = 0 + } + for (var i = 0; i < resultObj.deniedAlways.length; i++) { + var deniedAlwaysPermission = resultObj.deniedAlways[i]; + console.log('永久拒绝申请的权限:' + deniedAlwaysPermission); + result = -1 + } + resolve(result); + // 若所需权限被拒绝,则打开APP设置界面,可以在APP设置界面打开相应权限 + // if (result != 1) { + // gotoAppPermissionSetting() + // } + }, + function(error) { + console.log('申请权限错误:' + error.code + " = " + error.message); + resolve({ + code: error.code, + message: error.message + }); + } + ); + }); +} + +// 使用一个方法,根据参数判断权限 +function judgeIosPermission(permissionID) { + if (permissionID == "location") { + return judgeIosPermissionLocation() + } else if (permissionID == "camera") { + return judgeIosPermissionCamera() + } else if (permissionID == "photoLibrary") { + return judgeIosPermissionPhotoLibrary() + } else if (permissionID == "record") { + return judgeIosPermissionRecord() + } else if (permissionID == "push") { + return judgeIosPermissionPush() + } else if (permissionID == "contact") { + return judgeIosPermissionContact() + } else if (permissionID == "calendar") { + return judgeIosPermissionCalendar() + } else if (permissionID == "memo") { + return judgeIosPermissionMemo() + } + return false; +} + +// 跳转到**应用**的权限页面 +function gotoAppPermissionSetting() { + if (isIos) { + var UIApplication = plus.ios.import("UIApplication"); + var application2 = UIApplication.sharedApplication(); + var NSURL2 = plus.ios.import("NSURL"); + // var setting2 = NSURL2.URLWithString("prefs:root=LOCATION_SERVICES"); + var setting2 = NSURL2.URLWithString("app-settings:"); + application2.openURL(setting2); + + plus.ios.deleteObject(setting2); + plus.ios.deleteObject(NSURL2); + plus.ios.deleteObject(application2); + } else { + // console.log(plus.device.vendor); + 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); + } +} + +// 检查系统的设备服务是否开启 +// var checkSystemEnableLocation = async function () { +function checkSystemEnableLocation() { + if (isIos) { + var result = false; + var cllocationManger = plus.ios.import("CLLocationManager"); + var result = cllocationManger.locationServicesEnabled(); + console.log("系统定位开启:" + result); + plus.ios.deleteObject(cllocationManger); + return result; + } else { + var context = plus.android.importClass("android.content.Context"); + var locationManager = plus.android.importClass("android.location.LocationManager"); + var main = plus.android.runtimeMainActivity(); + var mainSvr = main.getSystemService(context.LOCATION_SERVICE); + var result = mainSvr.isProviderEnabled(locationManager.GPS_PROVIDER); + console.log("系统定位开启:" + result); + return result + } +} + +export default { + judgeIosPermission: judgeIosPermission, + requestAndroidPermission: requestAndroidPermission, + checkSystemEnableLocation: checkSystemEnableLocation, + gotoAppPermissionSetting: gotoAppPermissionSetting +} diff --git a/uni_modules/json-gps/package.json b/uni_modules/json-gps/package.json new file mode 100644 index 0000000..3689121 --- /dev/null +++ b/uni_modules/json-gps/package.json @@ -0,0 +1,76 @@ +{ + "id": "json-gps", + "displayName": "地理位置获取方法,支持在onShow生命周期使用集成权限判断和引导开启(包括设备权限和应用权限)", + "version": "1.0.0", + "description": "支持在onShow生命周期使用集成权限判断和引导开启(包括设备权限和应用权限)的地理位置获取方法", + "keywords": [ + "权限引导" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "JS SDK", + "通用 SDK" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "插件不采集任何数据", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "u", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/json-gps/readme.md b/uni_modules/json-gps/readme.md new file mode 100644 index 0000000..739335f --- /dev/null +++ b/uni_modules/json-gps/readme.md @@ -0,0 +1,30 @@ +### 插件简介: +支持在onShow生命周期,使用的集成权限判断和引导开启(包括设备权限和应用权限)的地理位置获取方法 + +### 插件背景: +实现获取用户地理位置,当手机未开启定位模块或应用无定位权限时,引导用户前往手机系统或应用权限设置页面。设置完回到应用界面自动重新获取。 +为了实现该效果,开发者把获取定位权限放在onShow生命周期,然而即使是原生开发,调用判断设备权限操作也会触发onShow生命周期,直接使用会导致死循环。因此本插件,二次封装用锁的方式控制该问题。 + +### 使用方式 +```js +import Gps from '@/uni_modules/json-gps/js_sdk/gps.js'; +const gps = new Gps() +export default { + async onShow() { + uni.showLoading({ + title:"获取定位中" + }); + let location = await gps.getLocation() + console.log(location); + if(location){ + uni.showToast({ + title: JSON.stringify(location), + icon: 'none' + }); + } + uni.hideLoading() + } +} +``` + +>本插件基于第三方插件:[App权限判断和提示](https://ext.dcloud.net.cn/plugin?id=594)二次封装而成 \ No newline at end of file diff --git a/uni_modules/json-interceptor-chooseImage/changelog.md b/uni_modules/json-interceptor-chooseImage/changelog.md new file mode 100644 index 0000000..512bce8 --- /dev/null +++ b/uni_modules/json-interceptor-chooseImage/changelog.md @@ -0,0 +1,6 @@ +## 1.0.2(2021-05-20) +修复IOS提示不准确,无摄像头权限提示了无法访问相册 +## 1.0.1(2021-05-20) +新增文档和示例代码 +## 1.0.0(2021-05-20) +第一版本发布 diff --git a/uni_modules/json-interceptor-chooseImage/js_sdk/main.js b/uni_modules/json-interceptor-chooseImage/js_sdk/main.js new file mode 100644 index 0000000..7cf7bc6 --- /dev/null +++ b/uni_modules/json-interceptor-chooseImage/js_sdk/main.js @@ -0,0 +1,70 @@ +export default function(){ + //当应用无访问摄像头/相册权限,引导跳到设置界面 + uni.addInterceptor('chooseImage', { + fail(e) { // 失败回调拦截 更多拦截器用法 [详情](https://uniapp.dcloud.io/api/interceptor?id=addinterceptor) + console.log(e); + if (uni.getSystemInfoSync().platform == "android" && e.errMsg == 'chooseImage:fail No Permission') { + if (e.code === 11) { + uni.showModal({ + title: "无法访问摄像头", + content: "当前无摄像头访问权限,建议前往设置", + confirmText: "前往设置", + success(e) { + if (e.confirm) { + gotoAppPermissionSetting() + } + } + }); + } else { + uni.showModal({ + title: "无法访问相册", + content: "当前无系统相册访问权限,建议前往设置", + confirmText: "前往设置", + success(e) { + if (e.confirm) { + gotoAppPermissionSetting() + } + } + }); + } + } else if (e.errCode === 2&&e.errMsg == "chooseImage:fail No filming permission") { + console.log('e.errMsg === 2 ios无法拍照权限 '); + // 注:e.errCode === 8 ios无从相册选择图片的权限 api已内置无需自己用拦截器实现 + uni.showModal({ + title: "无法访问摄像头", + content: "当前无摄像头访问权限,建议前往设置", + confirmText: "前往设置", + success(e) { + if (e.confirm) { + gotoAppPermissionSetting() + } + } + }); + } + } + }) + + //跳转到**应用**的权限页面 参考来源:https://ext.dcloud.net.cn/plugin?id=594 + function gotoAppPermissionSetting() { + if (uni.getSystemInfoSync().platform == "ios") { + 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); + } + } +} \ No newline at end of file diff --git a/uni_modules/json-interceptor-chooseImage/package.json b/uni_modules/json-interceptor-chooseImage/package.json new file mode 100644 index 0000000..9b81e6a --- /dev/null +++ b/uni_modules/json-interceptor-chooseImage/package.json @@ -0,0 +1,76 @@ +{ + "id": "json-interceptor-chooseImage", + "displayName": "拦截器应用示例 — 图片选择", + "version": "1.0.2", + "description": "当选择图片遇到权限问题时引导用户快捷打开系统设置", + "keywords": [ + "interceptor,拦截器,相册权限" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "JS SDK", + "通用 SDK" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "n", + "Android Browser": "n", + "微信浏览器(Android)": "n", + "QQ浏览器(Android)": "n" + }, + "H5-pc": { + "Chrome": "n", + "IE": "n", + "Edge": "n", + "Firefox": "n", + "Safari": "n" + }, + "小程序": { + "微信": "n", + "阿里": "n", + "百度": "n", + "字节跳动": "n", + "QQ": "n" + }, + "快应用": { + "华为": "n", + "联盟": "n" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/json-interceptor-chooseImage/readme.md b/uni_modules/json-interceptor-chooseImage/readme.md new file mode 100644 index 0000000..e9d98fb --- /dev/null +++ b/uni_modules/json-interceptor-chooseImage/readme.md @@ -0,0 +1,30 @@ +拦截器顾名思义,是在框架方法执行的各个环节(包含:拦截前触发、成功回调拦截、失败回调拦截、完成回调拦截) +插入逻辑,篡改参数或终止运行。 +#### 优势 +- 这种方式相当于改写了api的内部逻辑,相比语法糖他更加直观,不影响ide的代码提示。 +- 将常规固定的逻辑封装到框架的api内,且支持个性化设计。如你可以在本插件路径:`/uni_modules/json-interceptor-chooseImage/js_sdk/main.js`修改弹出框元素,绘制更漂亮的样式和文字说明。 + +#### 使用示例,App.vue页代码如下: +``` + +``` + +> 跳转到**应用**的权限页面 参考来源:[https://ext.dcloud.net.cn/plugin?id=594](https://ext.dcloud.net.cn/plugin?id=594) 感谢作者@DCloud_heavensoft diff --git a/uni_modules/uni-badge/changelog.md b/uni_modules/uni-badge/changelog.md new file mode 100644 index 0000000..4ca8c5b --- /dev/null +++ b/uni_modules/uni-badge/changelog.md @@ -0,0 +1,22 @@ +## 1.1.6(2021-09-22) +- 修复 在字节小程序上样式不生效的 bug +## 1.1.5(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.4(2021-07-29) +- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性 +## 1.1.3(2021-06-24) +- 优化 示例项目 +## 1.1.1(2021-05-12) +- 新增 组件示例地址 +## 1.1.0(2021-05-12) +- 新增 uni-badge 的 absolute 属性,支持定位 +- 新增 uni-badge 的 offset 属性,支持定位偏移 +- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点 +- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+ +- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式 +## 1.0.7(2021-05-07) +- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug +- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug +- 新增 uni-badge 属性 custom-style, 支持自定义样式 +## 1.0.6(2021-02-04) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-badge/components/uni-badge/uni-badge.vue b/uni_modules/uni-badge/components/uni-badge/uni-badge.vue new file mode 100644 index 0000000..f3869c4 --- /dev/null +++ b/uni_modules/uni-badge/components/uni-badge/uni-badge.vue @@ -0,0 +1,253 @@ + + + + + diff --git a/uni_modules/uni-badge/package.json b/uni_modules/uni-badge/package.json new file mode 100644 index 0000000..4efafd5 --- /dev/null +++ b/uni_modules/uni-badge/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-badge", + "displayName": "uni-badge 数字角标", + "version": "1.1.6", + "description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。", + "keywords": [ + "", + "badge", + "uni-ui", + "uniui", + "数字角标", + "徽章" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "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" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-badge/readme.md b/uni_modules/uni-badge/readme.md new file mode 100644 index 0000000..d29680b --- /dev/null +++ b/uni_modules/uni-badge/readme.md @@ -0,0 +1,58 @@ + + +## Badge 数字角标 +> **组件名:uni-badge** +> 代码块: `uBadge` + + +数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景, + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + + + + + + +``` + + +## API + +### Badge Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|text |String |- |角标内容 | +|type |String |default|颜色类型,可选值:default(灰色)、primary(蓝色)、success(绿色)、warning(黄色)、error(红色)| +|size |String |normal |Badge 大小,可取值:normal、small | +|is-dot |Boolean|false |不展示数字,只有一个小点 | +|max-num |String/Numbuer|99 |展示封顶的数字值,超过 99 显示99+ | +|custom-style |Object | {} |自定义 Badge 样式, 样式对象语法 | +|inverted |Boolean|false |是否无需背景颜色,为 true 时,背景颜色将变为文字的字体颜色 | +|absolute (不支持 nvue) |String| rightTop|开启绝对定位, 角标将定位到其包裹的标签的四个角上,可选值: rightTop(右上角)、rightBottom(右下角)、leftBottom(左下角) 、leftTop(左上角) | +|offset |Array[number]| [0, 0]|距定位角中心点的偏移量,[-10, -10] 表示向 absolute 指定的方向偏移 10px,[10, 10] 表示向 absolute 指定的反方向偏移 10px,只有存在 absolute 属性时有效,与absolute 的值一一对应(例如:值为rightTop, 对应 offset 为 [right, Top])| + +### Badge Events + +|事件名 |事件说明 |返回参数 | +|:-: |:-: |:-: | +|@click |点击 Badge 触发事件| - | + + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/badge/badge](https://hellouniapp.dcloud.net.cn/pages/extUI/badge/badge) \ No newline at end of file diff --git a/uni_modules/uni-calendar/changelog.md b/uni_modules/uni-calendar/changelog.md new file mode 100644 index 0000000..bd89749 --- /dev/null +++ b/uni_modules/uni-calendar/changelog.md @@ -0,0 +1,10 @@ +## 1.4.2(2021-08-24) +- 新增 支持国际化 +## 1.4.1(2021-08-05) +- 修复 弹出层被 tabbar 遮盖 bug +## 1.4.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.3.16(2021-05-12) +- 新增 组件示例地址 +## 1.3.15(2021-02-04) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-calendar/components/uni-calendar/calendar.js b/uni_modules/uni-calendar/components/uni-calendar/calendar.js new file mode 100644 index 0000000..b8d7d6f --- /dev/null +++ b/uni_modules/uni-calendar/components/uni-calendar/calendar.js @@ -0,0 +1,546 @@ +/** +* @1900-2100区间内的公历、农历互转 +* @charset UTF-8 +* @github https://github.com/jjonline/calendar.js +* @Author Jea杨(JJonline@JJonline.Cn) +* @Time 2014-7-21 +* @Time 2016-8-13 Fixed 2033hex、Attribution Annals +* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug +* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year +* @Version 1.0.3 +* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0] +* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0] +*/ +/* eslint-disable */ +var calendar = { + + /** + * 农历1900-2100的润大小信息表 + * @Array Of Property + * @return Hex + */ + lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909 + 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919 + 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929 + 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939 + 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949 + 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959 + 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969 + 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979 + 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989 + 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999 + 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009 + 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019 + 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029 + 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039 + 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049 + /** Add By JJonline@JJonline.Cn**/ + 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059 + 0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069 + 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079 + 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089 + 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099 + 0x0d520], // 2100 + + /** + * 公历每个月份的天数普通表 + * @Array Of Property + * @return Number + */ + solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31], + + /** + * 天干地支之天干速查表 + * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"] + * @return Cn string + */ + Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'], + + /** + * 天干地支之地支速查表 + * @Array Of Property + * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"] + * @return Cn string + */ + Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'], + + /** + * 天干地支之地支速查表<=>生肖 + * @Array Of Property + * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"] + * @return Cn string + */ + Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'], + + /** + * 24节气速查表 + * @Array Of Property + * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"] + * @return Cn string + */ + solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'], + + /** + * 1900-2100各年的24节气日期速查表 + * @Array Of Property + * @return 0x string For splice + */ + sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', + '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', + 'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f', + '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa', + '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f', + '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', + '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722', + '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f', + '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e', + '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722', + '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', + '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e', + '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', + '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', + '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa', + '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', + '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2', + '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722', + '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', + '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', + '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', + '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722', + '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', + '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', + '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721', + '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd', + '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35', + '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', + '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721', + '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5', + '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35', + '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', + '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd', + '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35', + '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'], + + /** + * 数字转中文速查表 + * @Array Of Property + * @trans ['日','一','二','三','四','五','六','七','八','九','十'] + * @return Cn string + */ + nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'], + + /** + * 日期转农历称呼速查表 + * @Array Of Property + * @trans ['初','十','廿','卅'] + * @return Cn string + */ + nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'], + + /** + * 月份转农历称呼速查表 + * @Array Of Property + * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊'] + * @return Cn string + */ + nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'], + + /** + * 返回农历y年一整年的总天数 + * @param lunar Year + * @return Number + * @eg:var count = calendar.lYearDays(1987) ;//count=387 + */ + lYearDays: function (y) { + var i; var sum = 348 + for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 } + return (sum + this.leapDays(y)) + }, + + /** + * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0 + * @param lunar Year + * @return Number (0-12) + * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6 + */ + leapMonth: function (y) { // 闰字编码 \u95f0 + return (this.lunarInfo[y - 1900] & 0xf) + }, + + /** + * 返回农历y年闰月的天数 若该年没有闰月则返回0 + * @param lunar Year + * @return Number (0、29、30) + * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29 + */ + leapDays: function (y) { + if (this.leapMonth(y)) { + return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29) + } + return (0) + }, + + /** + * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法 + * @param lunar Year + * @return Number (-1、29、30) + * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29 + */ + monthDays: function (y, m) { + if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1 + return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29) + }, + + /** + * 返回公历(!)y年m月的天数 + * @param solar Year + * @return Number (-1、28、29、30、31) + * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30 + */ + solarDays: function (y, m) { + if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1 + var ms = m - 1 + if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29 + return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28) + } else { + return (this.solarMonth[ms]) + } + }, + + /** + * 农历年份转换为干支纪年 + * @param lYear 农历年的年份数 + * @return Cn string + */ + toGanZhiYear: function (lYear) { + var ganKey = (lYear - 3) % 10 + var zhiKey = (lYear - 3) % 12 + if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干 + if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支 + return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1] + }, + + /** + * 公历月、日判断所属星座 + * @param cMonth [description] + * @param cDay [description] + * @return Cn string + */ + toAstro: function (cMonth, cDay) { + var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf' + var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22] + return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座 + }, + + /** + * 传入offset偏移量返回干支 + * @param offset 相对甲子的偏移量 + * @return Cn string + */ + toGanZhi: function (offset) { + return this.Gan[offset % 10] + this.Zhi[offset % 12] + }, + + /** + * 传入公历(!)y年获得该年第n个节气的公历日期 + * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起 + * @return day Number + * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春 + */ + getTerm: function (y, n) { + if (y < 1900 || y > 2100) { return -1 } + if (n < 1 || n > 24) { return -1 } + var _table = this.sTermInfo[y - 1900] + var _info = [ + parseInt('0x' + _table.substr(0, 5)).toString(), + parseInt('0x' + _table.substr(5, 5)).toString(), + parseInt('0x' + _table.substr(10, 5)).toString(), + parseInt('0x' + _table.substr(15, 5)).toString(), + parseInt('0x' + _table.substr(20, 5)).toString(), + parseInt('0x' + _table.substr(25, 5)).toString() + ] + var _calday = [ + _info[0].substr(0, 1), + _info[0].substr(1, 2), + _info[0].substr(3, 1), + _info[0].substr(4, 2), + + _info[1].substr(0, 1), + _info[1].substr(1, 2), + _info[1].substr(3, 1), + _info[1].substr(4, 2), + + _info[2].substr(0, 1), + _info[2].substr(1, 2), + _info[2].substr(3, 1), + _info[2].substr(4, 2), + + _info[3].substr(0, 1), + _info[3].substr(1, 2), + _info[3].substr(3, 1), + _info[3].substr(4, 2), + + _info[4].substr(0, 1), + _info[4].substr(1, 2), + _info[4].substr(3, 1), + _info[4].substr(4, 2), + + _info[5].substr(0, 1), + _info[5].substr(1, 2), + _info[5].substr(3, 1), + _info[5].substr(4, 2) + ] + return parseInt(_calday[n - 1]) + }, + + /** + * 传入农历数字月份返回汉语通俗表示法 + * @param lunar month + * @return Cn string + * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月' + */ + toChinaMonth: function (m) { // 月 => \u6708 + if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1 + var s = this.nStr3[m - 1] + s += '\u6708'// 加上月字 + return s + }, + + /** + * 传入农历日期数字返回汉字表示法 + * @param lunar day + * @return Cn string + * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一' + */ + toChinaDay: function (d) { // 日 => \u65e5 + var s + switch (d) { + case 10: + s = '\u521d\u5341'; break + case 20: + s = '\u4e8c\u5341'; break + break + case 30: + s = '\u4e09\u5341'; break + break + default : + s = this.nStr2[Math.floor(d / 10)] + s += this.nStr1[d % 10] + } + return (s) + }, + + /** + * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春” + * @param y year + * @return Cn string + * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔' + */ + getAnimal: function (y) { + return this.Animals[(y - 4) % 12] + }, + + /** + * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON + * @param y solar year + * @param m solar month + * @param d solar day + * @return JSON object + * @eg:console.log(calendar.solar2lunar(1987,11,01)); + */ + solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31 + // 年份限定、上限 + if (y < 1900 || y > 2100) { + return -1// undefined转换为数字变为NaN + } + // 公历传参最下限 + if (y == 1900 && m == 1 && d < 31) { + return -1 + } + // 未传参 获得当天 + if (!y) { + var objDate = new Date() + } else { + var objDate = new Date(y, parseInt(m) - 1, d) + } + var i; var leap = 0; var temp = 0 + // 修正ymd参数 + var y = objDate.getFullYear() + var m = objDate.getMonth() + 1 + var d = objDate.getDate() + var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000 + for (i = 1900; i < 2101 && offset > 0; i++) { + temp = this.lYearDays(i) + offset -= temp + } + if (offset < 0) { + offset += temp; i-- + } + + // 是否今天 + var isTodayObj = new Date() + var isToday = false + if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) { + isToday = true + } + // 星期几 + var nWeek = objDate.getDay() + var cWeek = this.nStr1[nWeek] + // 数字表示周几顺应天朝周一开始的惯例 + if (nWeek == 0) { + nWeek = 7 + } + // 农历年 + var year = i + var leap = this.leapMonth(i) // 闰哪个月 + var isLeap = false + + // 效验闰月 + for (i = 1; i < 13 && offset > 0; i++) { + // 闰月 + if (leap > 0 && i == (leap + 1) && isLeap == false) { + --i + isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数 + } else { + temp = this.monthDays(year, i)// 计算农历普通月天数 + } + // 解除闰月 + if (isLeap == true && i == (leap + 1)) { isLeap = false } + offset -= temp + } + // 闰月导致数组下标重叠取反 + if (offset == 0 && leap > 0 && i == leap + 1) { + if (isLeap) { + isLeap = false + } else { + isLeap = true; --i + } + } + if (offset < 0) { + offset += temp; --i + } + // 农历月 + var month = i + // 农历日 + var day = offset + 1 + // 天干地支处理 + var sm = m - 1 + var gzY = this.toGanZhiYear(year) + + // 当月的两个节气 + // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year` + var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始 + var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始 + + // 依据12节气修正干支月 + var gzM = this.toGanZhi((y - 1900) * 12 + m + 11) + if (d >= firstNode) { + gzM = this.toGanZhi((y - 1900) * 12 + m + 12) + } + + // 传入的日期的节气与否 + var isTerm = false + var Term = null + if (firstNode == d) { + isTerm = true + Term = this.solarTerm[m * 2 - 2] + } + if (secondNode == d) { + isTerm = true + Term = this.solarTerm[m * 2 - 1] + } + // 日柱 当月一日与 1900/1/1 相差天数 + var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10 + var gzD = this.toGanZhi(dayCyclical + d - 1) + // 该日期所属的星座 + var astro = this.toAstro(m, d) + + return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro } + }, + + /** + * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON + * @param y lunar year + * @param m lunar month + * @param d lunar day + * @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可] + * @return JSON object + * @eg:console.log(calendar.lunar2solar(1987,9,10)); + */ + lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1 + var isLeapMonth = !!isLeapMonth + var leapOffset = 0 + var leapMonth = this.leapMonth(y) + var leapDay = this.leapDays(y) + if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同 + if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值 + var day = this.monthDays(y, m) + var _day = day + // bugFix 2016-9-25 + // if month is leap, _day use leapDays method + if (isLeapMonth) { + _day = this.leapDays(y, m) + } + if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验 + + // 计算农历的时间差 + var offset = 0 + for (var i = 1900; i < y; i++) { + offset += this.lYearDays(i) + } + var leap = 0; var isAdd = false + for (var i = 1; i < m; i++) { + leap = this.leapMonth(y) + if (!isAdd) { // 处理闰月 + if (leap <= i && leap > 0) { + offset += this.leapDays(y); isAdd = true + } + } + offset += this.monthDays(y, i) + } + // 转换闰月农历 需补充该年闰月的前一个月的时差 + if (isLeapMonth) { offset += day } + // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点) + var stmap = Date.UTC(1900, 1, 30, 0, 0, 0) + var calObj = new Date((offset + d - 31) * 86400000 + stmap) + var cY = calObj.getUTCFullYear() + var cM = calObj.getUTCMonth() + 1 + var cD = calObj.getUTCDate() + + return this.solar2lunar(cY, cM, cD) + } +} + +export default calendar diff --git a/uni_modules/uni-calendar/components/uni-calendar/i18n/en.json b/uni_modules/uni-calendar/components/uni-calendar/i18n/en.json new file mode 100644 index 0000000..fcbd13c --- /dev/null +++ b/uni_modules/uni-calendar/components/uni-calendar/i18n/en.json @@ -0,0 +1,12 @@ +{ + "uni-calender.ok": "ok", + "uni-calender.cancel": "cancel", + "uni-calender.today": "today", + "uni-calender.MON": "MON", + "uni-calender.TUE": "TUE", + "uni-calender.WED": "WED", + "uni-calender.THU": "THU", + "uni-calender.FRI": "FRI", + "uni-calender.SAT": "SAT", + "uni-calender.SUN": "SUN" +} diff --git a/uni_modules/uni-calendar/components/uni-calendar/i18n/index.js b/uni_modules/uni-calendar/components/uni-calendar/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uni_modules/uni-calendar/components/uni-calendar/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json b/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json new file mode 100644 index 0000000..1ca43de --- /dev/null +++ b/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json @@ -0,0 +1,12 @@ +{ + "uni-calender.ok": "确定", + "uni-calender.cancel": "取消", + "uni-calender.today": "今日", + "uni-calender.SUN": "日", + "uni-calender.MON": "一", + "uni-calender.TUE": "二", + "uni-calender.WED": "三", + "uni-calender.THU": "四", + "uni-calender.FRI": "五", + "uni-calender.SAT": "六" +} diff --git a/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json b/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json new file mode 100644 index 0000000..e0fe33b --- /dev/null +++ b/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json @@ -0,0 +1,12 @@ +{ + "uni-calender.ok": "確定", + "uni-calender.cancel": "取消", + "uni-calender.today": "今日", + "uni-calender.SUN": "日", + "uni-calender.MON": "一", + "uni-calender.TUE": "二", + "uni-calender.WED": "三", + "uni-calender.THU": "四", + "uni-calender.FRI": "五", + "uni-calender.SAT": "六" +} diff --git a/uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue b/uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue new file mode 100644 index 0000000..0353011 --- /dev/null +++ b/uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue b/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue new file mode 100644 index 0000000..401a2de --- /dev/null +++ b/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue @@ -0,0 +1,547 @@ + + + + + diff --git a/uni_modules/uni-calendar/components/uni-calendar/util.js b/uni_modules/uni-calendar/components/uni-calendar/util.js new file mode 100644 index 0000000..37f4432 --- /dev/null +++ b/uni_modules/uni-calendar/components/uni-calendar/util.js @@ -0,0 +1,352 @@ +import CALENDAR from './calendar.js' + +class Calendar { + constructor({ + date, + selected, + startDate, + endDate, + range + } = {}) { + // 当前日期 + this.date = this.getDate(new Date()) // 当前初入日期 + // 打点信息 + this.selected = selected || []; + // 范围开始 + this.startDate = startDate + // 范围结束 + this.endDate = endDate + this.range = range + // 多选状态 + this.cleanMultipleStatus() + // 每周日期 + this.weeks = {} + // this._getWeek(this.date.fullDate) + } + /** + * 设置日期 + * @param {Object} date + */ + setDate(date) { + this.selectDate = this.getDate(date) + this._getWeek(this.selectDate.fullDate) + } + + /** + * 清理多选状态 + */ + cleanMultipleStatus() { + this.multipleStatus = { + before: '', + after: '', + data: [] + } + } + + /** + * 重置开始日期 + */ + resetSatrtDate(startDate) { + // 范围开始 + this.startDate = startDate + + } + + /** + * 重置结束日期 + */ + resetEndDate(endDate) { + // 范围结束 + this.endDate = endDate + } + + /** + * 获取任意时间 + */ + getDate(date, AddDayCount = 0, str = 'day') { + if (!date) { + date = new Date() + } + if (typeof date !== 'object') { + date = date.replace(/-/g, '/') + } + const dd = new Date(date) + switch (str) { + case 'day': + dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期 + break + case 'month': + if (dd.getDate() === 31) { + dd.setDate(dd.getDate() + AddDayCount) + } else { + dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期 + } + break + case 'year': + dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期 + break + } + const y = dd.getFullYear() + const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0 + const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0 + return { + fullDate: y + '-' + m + '-' + d, + year: y, + month: m, + date: d, + day: dd.getDay() + } + } + + + /** + * 获取上月剩余天数 + */ + _getLastMonthDays(firstDay, full) { + let dateArr = [] + for (let i = firstDay; i > 0; i--) { + const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate() + dateArr.push({ + date: beforeDate, + month: full.month - 1, + lunar: this.getlunar(full.year, full.month - 1, beforeDate), + disable: true + }) + } + return dateArr + } + /** + * 获取本月天数 + */ + _currentMonthDys(dateData, full) { + let dateArr = [] + let fullDate = this.date.fullDate + for (let i = 1; i <= dateData; i++) { + let isinfo = false + let nowDate = full.year + '-' + (full.month < 10 ? + full.month : full.month) + '-' + (i < 10 ? + '0' + i : i) + // 是否今天 + let isDay = fullDate === nowDate + // 获取打点信息 + let info = this.selected && this.selected.find((item) => { + if (this.dateEqual(nowDate, item.date)) { + return item + } + }) + + // 日期禁用 + let disableBefore = true + let disableAfter = true + if (this.startDate) { + let dateCompBefore = this.dateCompare(this.startDate, fullDate) + disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate) + } + + if (this.endDate) { + let dateCompAfter = this.dateCompare(fullDate, this.endDate) + disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate) + } + let multiples = this.multipleStatus.data + let checked = false + let multiplesStatus = -1 + if (this.range) { + if (multiples) { + multiplesStatus = multiples.findIndex((item) => { + return this.dateEqual(item, nowDate) + }) + } + if (multiplesStatus !== -1) { + checked = true + } + } + let data = { + fullDate: nowDate, + year: full.year, + date: i, + multiple: this.range ? checked : false, + beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate), + afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate), + month: full.month, + lunar: this.getlunar(full.year, full.month, i), + disable: !disableBefore || !disableAfter, + isDay + } + if (info) { + data.extraInfo = info + } + + dateArr.push(data) + } + return dateArr + } + /** + * 获取下月天数 + */ + _getNextMonthDays(surplus, full) { + let dateArr = [] + for (let i = 1; i < surplus + 1; i++) { + dateArr.push({ + date: i, + month: Number(full.month) + 1, + lunar: this.getlunar(full.year, Number(full.month) + 1, i), + disable: true + }) + } + return dateArr + } + + /** + * 获取当前日期详情 + * @param {Object} date + */ + getInfo(date) { + if (!date) { + date = new Date() + } + const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate) + return dateInfo + } + + /** + * 比较时间大小 + */ + dateCompare(startDate, endDate) { + // 计算截止时间 + startDate = new Date(startDate.replace('-', '/').replace('-', '/')) + // 计算详细项的截止时间 + endDate = new Date(endDate.replace('-', '/').replace('-', '/')) + if (startDate <= endDate) { + return true + } else { + return false + } + } + + /** + * 比较时间是否相等 + */ + dateEqual(before, after) { + // 计算截止时间 + before = new Date(before.replace('-', '/').replace('-', '/')) + // 计算详细项的截止时间 + after = new Date(after.replace('-', '/').replace('-', '/')) + if (before.getTime() - after.getTime() === 0) { + return true + } else { + return false + } + } + + + /** + * 获取日期范围内所有日期 + * @param {Object} begin + * @param {Object} end + */ + geDateAll(begin, end) { + var arr = [] + var ab = begin.split('-') + var ae = end.split('-') + var db = new Date() + db.setFullYear(ab[0], ab[1] - 1, ab[2]) + var de = new Date() + de.setFullYear(ae[0], ae[1] - 1, ae[2]) + var unixDb = db.getTime() - 24 * 60 * 60 * 1000 + var unixDe = de.getTime() - 24 * 60 * 60 * 1000 + for (var k = unixDb; k <= unixDe;) { + k = k + 24 * 60 * 60 * 1000 + arr.push(this.getDate(new Date(parseInt(k))).fullDate) + } + return arr + } + /** + * 计算阴历日期显示 + */ + getlunar(year, month, date) { + return CALENDAR.solar2lunar(year, month, date) + } + /** + * 设置打点 + */ + setSelectInfo(data, value) { + this.selected = value + this._getWeek(data) + } + + /** + * 获取多选状态 + */ + setMultiple(fullDate) { + let { + before, + after + } = this.multipleStatus + + if (!this.range) return + if (before && after) { + this.multipleStatus.before = '' + this.multipleStatus.after = '' + this.multipleStatus.data = [] + } else { + if (!before) { + this.multipleStatus.before = fullDate + } else { + this.multipleStatus.after = fullDate + if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after); + } else { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before); + } + } + } + this._getWeek(fullDate) + } + + /** + * 获取每周数据 + * @param {Object} dateData + */ + _getWeek(dateData) { + const { + fullDate, + year, + month, + date, + day + } = this.getDate(dateData) + let firstDay = new Date(year, month - 1, 1).getDay() + let currentDay = new Date(year, month, 0).getDate() + let dates = { + lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天 + currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数 + nextMonthDays: [], // 下个月开始几天 + weeks: [] + } + let canlender = [] + const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length) + dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData)) + canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays) + let weeks = {} + // 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天 + for (let i = 0; i < canlender.length; i++) { + if (i % 7 === 0) { + weeks[parseInt(i / 7)] = new Array(7) + } + weeks[parseInt(i / 7)][i % 7] = canlender[i] + } + this.canlender = canlender + this.weeks = weeks + } + + //静态方法 + // static init(date) { + // if (!this.instance) { + // this.instance = new Calendar(date); + // } + // return this.instance; + // } +} + + +export default Calendar diff --git a/uni_modules/uni-calendar/package.json b/uni_modules/uni-calendar/package.json new file mode 100644 index 0000000..8a5023b --- /dev/null +++ b/uni_modules/uni-calendar/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-calendar", + "displayName": "uni-calendar 日历", + "version": "1.4.2", + "description": "日历组件", + "keywords": [ + "uni-ui", + "uniui", + "日历", + "", + "打卡", + "日历选择" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-calendar/readme.md b/uni_modules/uni-calendar/readme.md new file mode 100644 index 0000000..4f3ca0e --- /dev/null +++ b/uni_modules/uni-calendar/readme.md @@ -0,0 +1,103 @@ + + +## Calendar 日历 +> **组件名:uni-calendar** +> 代码块: `uCalendar` + + +日历组件 + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js) +> - 仅支持自定义组件模式 +> - `date`属性传入的应该是一个 String ,如: 2019-06-27 ,而不是 new Date() +> - 通过 `insert` 属性来确定当前的事件是 @change 还是 @confirm 。理应合并为一个事件,但是为了区分模式,现使用两个事件,这里需要注意 +> - 弹窗模式下无法阻止后面的元素滚动,如有需要阻止,请在弹窗弹出后,手动设置滚动元素为不可滚动 + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + + +``` + +### 通过方法打开日历 + +需要设置 `insert` 为 `false` + +```html + + + + +``` + +```javascript + +export default { + data() { + return {}; + }, + methods: { + open(){ + this.$refs.calendar.open(); + }, + confirm(e) { + console.log(e); + } + } +}; + +``` + + +## API + +### Calendar Props + +| 属性名 | 类型 | 默认值| 说明 | +| | | +| date | String |- | 自定义当前时间,默认为今天 | +| lunar | Boolean | false | 显示农历 | +| startDate | String |- | 日期选择范围-开始日期 | +| endDate | String |- | 日期选择范围-结束日期 | +| range | Boolean | false | 范围选择 | +| insert | Boolean | false | 插入模式,可选值,ture:插入模式;false:弹窗模式;默认为插入模式 | +|clearDate |Boolean |true |弹窗模式是否清空上次选择内容 | +| selected | Array |- | 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] | +|showMonth | Boolean | true | 是否显示月份为背景 | + +### Calendar Events + +| 事件名 | 说明 |返回值| +| | | | +| open | 弹出日历组件,`insert :false` 时生效|- | + + + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar) \ No newline at end of file diff --git a/uni_modules/uni-captcha/changelog.md b/uni_modules/uni-captcha/changelog.md new file mode 100644 index 0000000..a710020 --- /dev/null +++ b/uni_modules/uni-captcha/changelog.md @@ -0,0 +1,4 @@ +## 0.1.1(2021-03-04) +- refresh不再读取上一条验证码状态 +## 0.1.0(2021-03-01) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-captcha/package.json b/uni_modules/uni-captcha/package.json new file mode 100644 index 0000000..7245ea0 --- /dev/null +++ b/uni_modules/uni-captcha/package.json @@ -0,0 +1,80 @@ +{ + "id": "uni-captcha", + "displayName": "uni-captcha", + "version": "0.1.1", + "description": "简洁、高效、灵活可配置的云端验证码模块", + "keywords": [ + "uniCloud", + "captcha", + "验证码", + "图形验证码", + "人机验证" +], + "repository": "https://gitee.com/dcloud/uni-captcha", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "uniCloud", + "云函数模板" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "u", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "u", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "u", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-captcha/readme.md b/uni_modules/uni-captcha/readme.md new file mode 100644 index 0000000..b23de18 --- /dev/null +++ b/uni_modules/uni-captcha/readme.md @@ -0,0 +1,92 @@ +## uni 验证码验证文档 + +> 用途:主要使用在登录、需要人机校验或其他限制调用的场景 + +> 验证码生成、校验都在服务端。页面使用返回的 base64 显示。[云端一体登录模板](https://ext.dcloud.net.cn/plugin?id=13)已集成,可下载体验。 + +> 数据表使用[opendb-verify-codes](https://gitee.com/dcloud/opendb/blob/master/collection/opendb-verify-codes/collection.json) + +### 获取验证码@create + +用法:`uniCaptcha.create(Object params);` + +**参数说明** + +| 字段 | 类型 | 必填 | 默认值 | 说明 | +| --------------- | ------ | ---- | ------- | ----------------------------------------------- | +| scene | String | 是 | 4 | 使用场景值,用于防止不同功能的验证码混用 | +| deviceId | String | - | - | 设备 id,如果不传,将自动从 uniCloud 上下文获取 | +| width | Number | - | 100 | 图片宽度 | +| height | Number | - | 40 | 图片高度 | +| backgroundColor | String | - | #FFFAE8 | 验证码背景色 | +| size | Number | - | 4 | 验证码长度,最多 6 个字符 | +| noise | Number | - | 4 | 验证码干扰线条数 | +| expiresDate | Number | - | 180 | 验证码过期时间(s) | + +**响应参数** + +| 字段 | 类型 | 说明 | +| ------------- | ------ | ------------------- | +| code | Number | 错误码,0 表示成功 | +| message | String | 详细信息 | +| captchaBase64 | String | 验证码:base64 格式 | + +`注意:` + +- 重新生成后,上条验证码作废 + +### 校验验证码@verify + +用法:`uniCaptcha.verify(Object params);` + +**参数说明** + +| 字段 | 类型 | 必填 | 默认值 | 说明 | +| -------- | ------ | ---- | ------ | ----------------------------------------------- | +| scene | String | 是 | - | 类型,用于防止不同功能的验证码混用 | +| captcha | String | 是 | - | 验证码 | +| deviceId | String | - | - | 设备 id,如果不传,将自动从 uniCloud 上下文获取 | + +**响应参数** + +| 字段 | 类型 | 说明 | +| ------- | ------ | ------------------ | +| code | Number | 错误码,0 表示成功 | +| message | String | 详细信息 | + +`注意:` + +- 若提示验证码失效,请重新获取 + +### 刷新验证码@refresh + +用法:`uniCaptcha.refresh(Object params);` + +**参数说明** + +| 字段 | 类型 | 必填 | 默认值 | 说明 | +| -------- | ------ | ---- | ------ | ----------------------------------------------- | +| scene | String | 是 | - | 类型,用于防止不同功能的验证码混用 | +| deviceId | String | - | - | 设备 id,如果不传,将自动从 uniCloud 上下文获取 | + +**响应参数** + +| 字段 | 类型 | 说明 | +| ------------- | ------ | ------------------- | +| code | Number | 错误码,0 表示成功 | +| message | String | 详细信息 | +| captchaBase64 | String | 验证码:base64 格式 | + +`注意:` + +- 支持传入 create 方法的所有参数,如果不传,则自动按照 deviceId 匹配上次生成时的配置生成新的验证码 + +## 错误码 + +_详细信息请查看 message 中查看_ + +| 模块 | 模块码 | 错误代码 | 错误信息 | +| :----: | :----: | :------: | :---------------------: | +| 验证码 | 100 | 01 | (10001)验证码生成失败 | +| | | 02 | (10002)验证码校验失败 | +| | | 03 | (10003)验证码刷新失败 | diff --git a/uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha/LICENSE.md b/uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha/LICENSE.md new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha/LICENSE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha/index.js b/uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha/index.js new file mode 100644 index 0000000..3bc88f4 --- /dev/null +++ b/uni_modules/uni-captcha/uniCloud/cloudfunctions/common/uni-captcha/index.js @@ -0,0 +1 @@ +"use strict";var t,e=(t=require("fs"))&&"object"==typeof t&&"default"in t?t.default:t,n="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function r(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}function o(t,e){return t(e={exports:{}},e.exports),e.exports}var i=o((function(t,e){Object.defineProperty(e,"__esModule",{value:!0}),e.font16x32=e.font12x24=e.font8x16=void 0;var n="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";e.font8x16={w:8,h:16,fonts:n,data:[[0,0,0,0,0,0,0,60,66,30,34,66,66,63,0,0],[0,0,0,192,64,64,64,88,100,66,66,66,100,88,0,0],[0,0,0,0,0,0,0,28,34,64,64,64,34,28,0,0],[0,0,0,6,2,2,2,30,34,66,66,66,38,27,0,0],[0,0,0,0,0,0,0,60,66,126,64,64,66,60,0,0],[0,0,0,15,17,16,16,126,16,16,16,16,16,124,0,0],[0,0,0,0,0,0,0,62,68,68,56,64,60,66,66,60],[0,0,0,192,64,64,64,92,98,66,66,66,66,231,0,0],[0,0,0,48,48,0,0,112,16,16,16,16,16,124,0,0],[0,0,0,12,12,0,0,28,4,4,4,4,4,4,68,120],[0,0,0,192,64,64,64,78,72,80,104,72,68,238,0,0],[0,0,0,112,16,16,16,16,16,16,16,16,16,124,0,0],[0,0,0,0,0,0,0,254,73,73,73,73,73,237,0,0],[0,0,0,0,0,0,0,220,98,66,66,66,66,231,0,0],[0,0,0,0,0,0,0,60,66,66,66,66,66,60,0,0],[0,0,0,0,0,0,0,216,100,66,66,66,68,120,64,224],[0,0,0,0,0,0,0,30,34,66,66,66,34,30,2,7],[0,0,0,0,0,0,0,238,50,32,32,32,32,248,0,0],[0,0,0,0,0,0,0,62,66,64,60,2,66,124,0,0],[0,0,0,0,0,16,16,124,16,16,16,16,16,12,0,0],[0,0,0,0,0,0,0,198,66,66,66,66,70,59,0,0],[0,0,0,0,0,0,0,231,66,36,36,40,16,16,0,0],[0,0,0,0,0,0,0,215,146,146,170,170,68,68,0,0],[0,0,0,0,0,0,0,110,36,24,24,24,36,118,0,0],[0,0,0,0,0,0,0,231,66,36,36,40,24,16,16,224],[0,0,0,0,0,0,0,126,68,8,16,16,34,126,0,0],[0,0,0,16,16,24,40,40,36,60,68,66,66,231,0,0],[0,0,0,248,68,68,68,120,68,66,66,66,68,248,0,0],[0,0,0,62,66,66,128,128,128,128,128,66,68,56,0,0],[0,0,0,248,68,66,66,66,66,66,66,66,68,248,0,0],[0,0,0,252,66,72,72,120,72,72,64,66,66,252,0,0],[0,0,0,252,66,72,72,120,72,72,64,64,64,224,0,0],[0,0,0,60,68,68,128,128,128,142,132,68,68,56,0,0],[0,0,0,231,66,66,66,66,126,66,66,66,66,231,0,0],[0,0,0,124,16,16,16,16,16,16,16,16,16,124,0,0],[0,0,0,62,8,8,8,8,8,8,8,8,8,8,136,240],[0,0,0,238,68,72,80,112,80,72,72,68,68,238,0,0],[0,0,0,224,64,64,64,64,64,64,64,64,66,254,0,0],[0,0,0,238,108,108,108,108,84,84,84,84,84,214,0,0],[0,0,0,199,98,98,82,82,74,74,74,70,70,226,0,0],[0,0,0,56,68,130,130,130,130,130,130,130,68,56,0,0],[0,0,0,252,66,66,66,66,124,64,64,64,64,224,0,0],[0,0,0,56,68,130,130,130,130,130,178,202,76,56,6,0],[0,0,0,252,66,66,66,124,72,72,68,68,66,227,0,0],[0,0,0,62,66,66,64,32,24,4,2,66,66,124,0,0],[0,0,0,254,146,16,16,16,16,16,16,16,16,56,0,0],[0,0,0,231,66,66,66,66,66,66,66,66,66,60,0,0],[0,0,0,231,66,66,68,36,36,40,40,24,16,16,0,0],[0,0,0,214,146,146,146,146,170,170,108,68,68,68,0,0],[0,0,0,231,66,36,36,24,24,24,36,36,66,231,0,0],[0,0,0,238,68,68,40,40,16,16,16,16,16,56,0,0],[0,0,0,126,132,4,8,8,16,32,32,66,66,252,0,0],[0,0,0,24,36,66,66,66,66,66,66,66,36,24,0,0],[0,0,0,16,112,16,16,16,16,16,16,16,16,124,0,0],[0,0,0,60,66,66,66,4,4,8,16,32,66,126,0,0],[0,0,0,60,66,66,4,24,4,2,2,66,68,56,0,0],[0,0,0,4,12,20,36,36,68,68,126,4,4,30,0,0],[0,0,0,126,64,64,64,88,100,2,2,66,68,56,0,0],[0,0,0,28,36,64,64,88,100,66,66,66,36,24,0,0],[0,0,0,126,68,68,8,8,16,16,16,16,16,16,0,0],[0,0,0,60,66,66,66,36,24,36,66,66,66,60,0,0],[0,0,0,24,36,66,66,66,38,26,2,2,36,56,0,0]]},e.font12x24={w:12,h:24,fonts:n,data:[[0,0,0,0,0,0,0,0,0,0,15,48,48,7,28,48,96,96,96,113,62,0,0,0,0,0,0,0,0,0,0,0,0,0,128,192,192,192,192,192,192,192,192,208,240,0,0,0],[0,0,0,0,16,112,48,48,48,48,51,60,56,48,48,48,48,48,48,56,47,0,0,0,0,0,0,0,0,0,0,0,0,0,128,192,96,96,96,96,96,96,64,192,128,0,0,0],[0,0,0,0,0,0,0,0,0,0,15,49,49,97,96,96,96,96,48,48,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,128,0,0,0,64,64,128,0,0,0,0],[0,0,0,0,0,1,0,0,0,0,30,49,48,96,96,96,96,96,32,49,30,0,0,0,0,0,0,0,64,192,192,192,192,192,192,192,192,192,192,192,192,192,192,224,128,0,0,0],[0,0,0,0,0,0,0,0,0,0,7,24,16,48,63,48,48,48,24,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,128,192,96,96,224,0,0,0,32,64,128,0,0,0],[0,0,0,0,0,3,6,12,12,12,127,12,12,12,12,12,12,12,12,12,63,0,0,0,0,0,0,0,0,192,96,96,0,0,192,0,0,0,0,0,0,0,0,0,128,0,0,0],[0,0,0,0,0,0,0,0,0,0,15,25,48,48,48,25,31,48,62,31,96,96,112,31,0,0,0,0,0,0,0,0,0,0,112,144,192,192,192,128,0,0,0,192,96,96,224,128],[0,0,0,0,16,112,48,48,48,48,55,56,48,48,48,48,48,48,48,48,121,0,0,0,0,0,0,0,0,0,0,0,0,0,128,192,192,192,192,192,192,192,192,192,224,0,0,0],[0,0,0,0,0,6,6,0,0,0,62,6,6,6,6,6,6,6,6,6,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,0,0,0],[0,0,0,0,0,1,1,0,0,0,15,1,1,1,1,1,1,1,1,1,1,1,51,62,0,0,0,0,0,128,128,0,0,0,128,128,128,128,128,128,128,128,128,128,128,128,0,0],[0,0,0,0,16,112,48,48,48,48,51,49,51,50,54,62,59,51,49,49,121,0,0,0,0,0,0,0,0,0,0,0,0,0,192,0,0,0,0,0,0,0,128,128,224,0,0,0],[0,0,0,0,2,62,6,6,6,6,6,6,6,6,6,6,6,6,6,6,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,0,0,0],[0,0,0,0,0,0,0,0,0,0,238,119,102,102,102,102,102,102,102,102,247,0,0,0,0,0,0,0,0,0,0,0,0,0,224,96,96,96,96,96,96,96,96,96,112,0,0,0],[0,0,0,0,0,0,0,0,0,0,115,60,48,48,48,48,48,48,48,48,121,0,0,0,0,0,0,0,0,0,0,0,0,0,128,192,192,192,192,192,192,192,192,192,224,0,0,0],[0,0,0,0,0,0,0,0,0,0,15,25,48,96,96,96,96,96,48,48,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,192,96,96,96,96,96,192,192,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,119,56,48,48,48,48,48,48,48,56,55,48,48,124,0,0,0,0,0,0,0,0,0,0,128,192,96,96,96,96,96,96,192,192,128,0,0,0],[0,0,0,0,0,0,0,0,0,0,30,49,48,96,96,96,96,96,32,49,30,0,0,3,0,0,0,0,0,0,0,0,0,0,64,192,192,192,192,192,192,192,192,192,192,192,192,224],[0,0,0,0,0,0,0,0,0,0,249,26,28,24,24,24,24,24,24,24,255,0,0,0,0,0,0,0,0,0,0,0,0,0,224,96,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,15,24,48,48,28,15,1,32,32,48,63,0,0,0,0,0,0,0,0,0,0,0,0,0,224,96,32,0,0,128,192,96,96,192,128,0,0,0],[0,0,0,0,0,0,4,4,12,12,127,12,12,12,12,12,12,12,12,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,64,64,128,0,0,0],[0,0,0,0,0,0,0,0,0,16,113,48,48,48,48,48,48,48,48,57,30,0,0,0,0,0,0,0,0,0,0,0,0,64,192,192,192,192,192,192,192,192,192,224,128,0,0,0],[0,0,0,0,0,0,0,0,0,0,124,56,24,24,12,12,12,7,7,7,2,0,0,0,0,0,0,0,0,0,0,0,0,0,240,96,64,64,128,128,128,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,247,99,99,103,55,53,57,57,57,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,176,32,32,32,32,192,192,192,192,128,128,0,0,0],[0,0,0,0,0,0,0,0,0,0,125,24,25,13,14,6,7,11,25,17,123,0,0,0,0,0,0,0,0,0,0,0,0,0,224,128,128,0,0,0,0,0,128,192,224,0,0,0],[0,0,0,0,0,0,0,0,0,0,125,56,24,24,13,13,13,6,6,2,4,4,40,56,0,0,0,0,0,0,0,0,0,0,224,128,128,128,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,63,33,35,3,7,6,14,12,28,24,63,0,0,0,0,0,0,0,0,0,0,0,0,0,192,128,128,0,0,0,0,32,32,96,192,0,0,0],[0,0,0,0,0,6,6,14,11,11,19,17,17,17,31,32,32,32,32,96,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,128,128,128,192,192,192,96,96,240,0,0,0],[0,0,0,0,0,255,97,96,96,96,96,97,127,96,96,96,96,96,96,96,255,0,0,0,0,0,0,0,0,0,128,192,192,192,192,128,0,192,64,96,96,96,96,192,128,0,0,0],[0,0,0,0,0,7,24,48,48,32,96,96,96,96,96,96,96,48,48,24,15,0,0,0,0,0,0,0,0,224,96,32,32,0,0,0,0,0,0,0,32,32,64,128,0,0,0,0],[0,0,0,0,0,254,97,96,96,96,96,96,96,96,96,96,96,96,96,99,254,0,0,0,0,0,0,0,0,0,128,192,192,96,96,96,96,96,96,96,96,192,192,128,0,0,0,0],[0,0,0,0,0,255,96,96,96,96,97,97,127,97,97,96,96,96,96,96,255,0,0,0,0,0,0,0,0,192,64,32,0,0,0,0,0,0,0,0,0,32,32,64,192,0,0,0],[0,0,0,0,0,255,96,96,96,96,97,97,127,97,97,96,96,96,96,96,240,0,0,0,0,0,0,0,0,192,192,32,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,15,24,48,48,32,96,96,96,96,99,96,96,48,48,24,15,0,0,0,0,0,0,0,0,64,192,64,64,0,0,0,0,0,240,192,192,192,192,192,0,0,0,0],[0,0,0,0,0,240,96,96,96,96,96,96,127,96,96,96,96,96,96,96,240,0,0,0,0,0,0,0,0,240,96,96,96,96,96,96,224,96,96,96,96,96,96,96,240,0,0,0],[0,0,0,0,0,63,6,6,6,6,6,6,6,6,6,6,6,6,6,6,63,0,0,0,0,0,0,0,0,192,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,0,0,0],[0,0,0,0,0,15,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,97,99,62,0,0,0,0,0,240,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,0,0],[0,0,0,0,0,243,96,97,98,98,100,108,124,118,103,99,99,97,96,96,241,0,0,0,0,0,0,0,0,224,128,0,0,0,0,0,0,0,0,0,128,128,192,224,240,0,0,0],[0,0,0,0,0,240,96,96,96,96,96,96,96,96,96,96,96,96,96,96,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,32,64,192,0,0,0],[0,0,0,0,0,240,112,112,112,89,89,89,89,90,78,78,78,78,68,68,228,0,0,0,0,0,0,0,0,240,224,224,224,96,96,96,96,96,96,96,96,96,96,96,240,0,0,0],[0,0,0,0,0,224,112,112,88,88,76,70,70,67,67,65,64,64,64,64,224,0,0,0,0,0,0,0,0,112,32,32,32,32,32,32,32,32,32,160,224,224,96,96,32,0,0,0],[0,0,0,0,0,15,25,48,48,96,96,96,96,96,96,96,96,48,48,25,15,0,0,0,0,0,0,0,0,0,128,192,64,96,96,96,96,96,96,96,96,64,192,128,0,0,0,0],[0,0,0,0,0,255,96,96,96,96,96,96,96,127,96,96,96,96,96,96,240,0,0,0,0,0,0,0,0,128,192,96,96,96,96,96,192,128,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,15,25,48,48,96,96,96,96,96,96,96,110,50,49,17,15,1,0,0,0,0,0,0,0,0,128,192,64,96,96,96,96,96,96,96,96,64,192,128,128,224,192,0],[0,0,0,0,0,255,96,96,96,96,96,96,127,102,99,99,97,97,96,96,240,0,0,0,0,0,0,0,0,128,192,96,96,96,96,192,0,0,0,0,128,128,192,192,112,0,0,0],[0,0,0,0,0,31,48,96,96,96,112,60,15,3,0,0,64,64,96,112,79,0,0,0,0,0,0,0,0,32,224,32,32,0,0,0,0,192,192,96,96,96,96,192,128,0,0,0],[0,0,0,0,0,127,70,134,134,6,6,6,6,6,6,6,6,6,6,6,15,0,0,0,0,0,0,0,0,224,32,16,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,240,96,96,96,96,96,96,96,96,96,96,96,96,96,48,31,0,0,0,0,0,0,0,0,112,32,32,32,32,32,32,32,32,32,32,32,32,32,64,128,0,0,0],[0,0,0,0,0,248,112,48,48,48,48,24,24,24,24,13,13,13,15,6,6,0,0,0,0,0,0,0,0,240,96,64,64,64,128,128,128,128,128,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,247,102,102,102,102,103,55,55,59,59,59,59,57,17,17,17,0,0,0,0,0,0,0,0,112,32,32,64,64,64,64,64,128,128,128,128,128,0,0,0,0,0,0],[0,0,0,0,0,121,48,24,24,25,13,14,6,6,7,11,11,25,17,48,121,0,0,0,0,0,0,0,0,224,192,128,128,0,0,0,0,0,0,0,128,128,128,192,224,0,0,0],[0,0,0,0,0,248,112,48,48,24,24,13,13,14,6,6,6,6,6,6,31,0,0,0,0,0,0,0,0,240,96,64,128,128,128,0,0,0,0,0,0,0,0,0,128,0,0,0],[0,0,0,0,0,63,32,65,1,3,3,3,6,6,12,12,24,24,56,48,127,0,0,0,0,0,0,0,0,224,192,192,128,128,0,0,0,0,0,0,0,32,32,64,192,0,0,0],[0,0,0,0,0,15,25,48,48,96,96,96,96,96,96,96,96,48,48,25,15,0,0,0,0,0,0,0,0,0,128,192,192,96,96,96,96,96,96,96,96,192,192,128,0,0,0,0],[0,0,0,0,0,2,6,62,6,6,6,6,6,6,6,6,6,6,6,6,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,0,0,0],[0,0,0,0,0,31,33,64,96,96,0,1,1,3,4,8,16,32,64,127,127,0,0,0,0,0,0,0,0,0,128,192,192,192,192,128,128,0,0,0,64,64,64,192,192,0,0,0],[0,0,0,0,0,30,35,97,97,97,1,3,14,1,0,0,96,96,96,33,31,0,0,0,0,0,0,0,0,0,0,128,128,128,128,0,0,128,128,192,192,192,192,128,0,0,0,0],[0,0,0,0,0,1,3,3,5,9,9,17,33,33,65,127,1,1,1,1,7,0,0,0,0,0,0,0,128,128,128,128,128,128,128,128,128,128,128,224,128,128,128,128,224,0,0,0],[0,0,0,0,0,63,63,32,32,32,32,47,49,32,0,0,96,96,65,33,31,0,0,0,0,0,0,0,0,192,192,0,0,0,0,0,128,192,192,192,192,192,128,128,0,0,0,0],[0,0,0,0,0,7,24,48,48,32,96,103,104,112,96,96,96,32,48,24,15,0,0,0,0,0,0,0,0,128,192,192,0,0,0,128,192,96,96,96,96,96,64,192,0,0,0,0],[0,0,0,0,0,31,63,48,32,32,0,1,1,2,2,2,6,6,6,6,6,0,0,0,0,0,0,0,0,224,224,64,128,128,128,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,31,48,96,96,96,112,60,15,51,32,96,96,96,96,48,15,0,0,0,0,0,0,0,0,128,192,96,96,96,64,192,0,128,192,96,96,96,96,192,128,0,0,0],[0,0,0,0,0,15,48,48,96,96,96,96,96,49,30,0,0,0,48,49,30,0,0,0,0,0,0,0,0,0,128,192,64,96,96,96,224,96,96,96,192,192,128,128,0,0,0,0]]},e.font16x32={w:16,h:32,fonts:n,data:[[0,0,0,0,0,0,0,0,0,0,0,0,0,15,24,48,48,0,1,14,56,48,96,96,96,48,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,96,48,48,48,240,48,48,48,48,48,50,242,28,0,0,0,0,0],[0,0,0,0,0,8,120,24,24,24,24,24,24,24,27,28,28,24,24,24,24,24,24,24,28,30,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,24,12,6,6,6,6,6,6,6,4,12,24,224,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,3,14,24,24,48,48,48,48,48,48,24,24,12,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,48,24,24,24,0,0,0,0,4,4,8,16,224,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,24,24,48,48,48,48,48,48,16,24,12,7,0,0,0,0,0,0,0,0,0,0,8,120,24,24,24,24,24,24,216,56,24,24,24,24,24,24,24,24,24,56,94,144,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,3,12,24,16,48,48,63,48,48,48,24,24,14,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,48,24,8,12,12,252,0,0,0,4,8,24,224,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,3,3,3,3,63,3,3,3,3,3,3,3,3,3,3,3,3,31,0,0,0,0,0,0,0,0,0,0,0,124,195,3,3,0,0,0,248,0,0,0,0,0,0,0,0,0,0,0,0,240,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,3,12,8,24,24,24,8,12,15,24,24,15,15,16,48,48,48,28,7,0,0,0,0,0,0,0,0,0,0,0,0,0,238,54,24,24,24,24,24,48,224,0,0,240,252,14,6,6,6,28,240],[0,0,0,0,0,8,120,24,24,24,24,24,24,25,27,28,24,24,24,24,24,24,24,24,24,24,126,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,24,12,12,12,12,12,12,12,12,12,12,12,63,0,0,0,0,0],[0,0,0,0,0,0,1,1,1,0,0,0,0,31,1,1,1,1,1,1,1,1,1,1,1,1,31,0,0,0,0,0,0,0,0,0,0,0,192,192,192,0,0,0,128,128,128,128,128,128,128,128,128,128,128,128,128,128,248,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,24,15,0,0,0,0,0,0,28,28,28,0,0,0,8,248,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,48,96,192],[0,0,0,0,0,8,120,24,24,24,24,24,24,24,24,24,24,24,25,27,28,24,24,24,24,24,126,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,48,96,192,128,128,128,192,224,96,48,56,24,62,0,0,0,0,0],[0,0,0,0,0,0,31,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,31,0,0,0,0,0,0,0,0,0,0,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,248,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,16,119,57,49,49,49,49,49,49,49,49,49,49,49,123,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,56,204,140,140,140,140,140,140,140,140,140,140,140,222,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,8,120,27,28,24,24,24,24,24,24,24,24,24,24,126,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,24,12,12,12,12,12,12,12,12,12,12,12,63,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,3,14,8,24,48,48,48,48,48,48,24,24,12,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,224,56,12,12,6,6,6,6,6,6,12,12,24,224,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,8,121,26,28,24,24,24,24,24,24,24,24,28,30,25,24,24,24,24,126,0,0,0,0,0,0,0,0,0,0,0,0,0,240,24,12,4,6,6,6,6,6,6,12,12,24,224,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,3,12,24,24,48,48,48,48,48,48,16,24,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,196,60,28,12,12,12,12,12,12,12,12,28,60,204,12,12,12,12,63],[0,0,0,0,0,0,0,0,0,0,0,0,6,126,6,6,7,7,6,6,6,6,6,6,6,6,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,102,134,0,0,0,0,0,0,0,0,0,0,224,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,7,12,24,24,24,14,7,1,0,32,32,48,56,55,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,228,28,12,4,0,0,192,240,56,12,12,12,24,240,0,0,0,0,0],[0,0,0,0,0,0,0,0,1,1,1,3,7,63,3,3,3,3,3,3,3,3,3,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,0,0,0,0,0,0,0,0,0,4,4,136,240,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,8,120,24,24,24,24,24,24,24,24,24,24,24,12,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,60,12,12,12,12,12,12,12,12,12,12,28,47,200,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,126,24,24,28,12,12,14,6,6,7,3,3,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,60,24,16,16,32,32,64,64,64,128,128,128,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,251,113,48,49,49,25,25,26,26,14,14,14,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,239,198,196,196,196,200,200,104,104,112,112,112,32,32,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,63,14,14,7,3,3,1,1,2,6,4,8,24,124,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,16,32,32,64,128,192,192,224,96,48,48,24,126,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,126,24,24,12,12,12,6,6,3,3,3,1,1,1,1,1,1,50,60,0,0,0,0,0,0,0,0,0,0,0,0,0,62,24,16,16,16,32,32,32,64,64,64,128,128,128,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,63,48,32,32,0,1,1,3,7,14,12,28,56,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,48,112,96,192,192,128,0,0,4,4,12,24,248,0,0,0,0,0],[0,0,0,0,0,0,3,3,3,2,6,4,4,4,12,8,8,8,31,16,16,16,48,32,32,96,248,0,0,0,0,0,0,0,0,0,0,128,128,128,128,128,192,192,192,192,96,96,96,96,240,48,48,48,48,24,24,24,62,0,0,0,0,0],[0,0,0,0,0,0,127,24,24,24,24,24,24,24,24,31,24,24,24,24,24,24,24,24,24,24,127,0,0,0,0,0,0,0,0,0,0,0,224,56,28,12,12,12,12,24,48,224,24,12,4,6,6,6,6,6,12,24,240,0,0,0,0,0],[0,0,0,0,0,0,3,6,8,24,48,48,32,96,96,96,96,96,96,96,96,48,48,48,24,12,3,0,0,0,0,0,0,0,0,0,0,0,228,28,12,4,2,2,0,0,0,0,0,0,0,0,0,2,2,4,12,24,224,0,0,0,0,0],[0,0,0,0,0,0,127,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,127,0,0,0,0,0,0,0,0,0,0,0,192,112,24,12,12,12,6,6,6,6,6,6,6,6,6,12,12,8,24,112,192,0,0,0,0,0],[0,0,0,0,0,0,127,24,24,24,24,24,24,24,24,31,24,24,24,24,24,24,24,24,24,24,127,0,0,0,0,0,0,0,0,0,0,0,252,12,4,6,2,0,16,16,48,240,48,16,16,0,0,0,2,2,4,12,252,0,0,0,0,0],[0,0,0,0,0,0,127,24,24,24,24,24,24,24,24,31,24,24,24,24,24,24,24,24,24,24,126,0,0,0,0,0,0,0,0,0,0,0,254,14,2,3,1,0,8,8,24,248,24,8,8,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,3,14,8,24,48,48,32,96,96,96,96,96,96,96,96,48,48,24,24,12,3,0,0,0,0,0,0,0,0,0,0,0,200,56,8,8,4,4,0,0,0,0,0,0,63,12,12,12,12,12,12,16,224,0,0,0,0,0],[0,0,0,0,0,0,252,48,48,48,48,48,48,48,48,48,63,48,48,48,48,48,48,48,48,48,252,0,0,0,0,0,0,0,0,0,0,0,126,24,24,24,24,24,24,24,24,24,248,24,24,24,24,24,24,24,24,24,126,0,0,0,0,0],[0,0,0,0,0,0,31,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,31,0,0,0,0,0,0,0,0,0,0,0,248,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,248,0,0,0,0,0],[0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,112,112,113,63,0,0,0,0,0,0,254,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,96,192,128,0],[0,0,0,0,0,0,126,24,24,24,24,24,24,25,25,27,29,28,24,24,24,24,24,24,24,24,126,0,0,0,0,0,0,0,0,0,0,0,62,24,16,32,96,64,128,128,128,128,192,192,224,96,112,48,56,24,12,12,63,0,0,0,0,0],[0,0,0,0,0,0,126,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,4,12,252,0,0,0,0,0],[0,0,0,0,0,0,248,56,56,56,56,44,44,44,44,46,38,38,38,38,35,35,35,35,35,33,249,0,0,0,0,0,0,0,0,0,0,0,31,28,28,28,60,44,44,44,108,76,76,76,76,140,140,140,140,12,12,12,63,0,0,0,0,0],[0,0,0,0,0,0,248,56,60,44,44,46,38,39,35,35,33,33,32,32,32,32,32,32,32,32,248,0,0,0,0,0,0,0,0,0,0,0,62,8,8,8,8,8,8,8,8,136,136,200,200,232,104,120,56,56,56,24,24,0,0,0,0,0],[0,0,0,0,0,0,3,12,24,16,48,48,96,96,96,96,96,96,96,96,96,48,48,16,24,12,3,0,0,0,0,0,0,0,0,0,0,0,192,48,24,8,12,12,6,6,6,6,6,6,6,6,6,4,12,8,24,48,192,0,0,0,0,0],[0,0,0,0,0,0,127,24,24,24,24,24,24,24,24,24,31,24,24,24,24,24,24,24,24,24,126,0,0,0,0,0,0,0,0,0,0,0,240,24,12,6,6,6,6,6,12,24,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,3,12,24,48,48,48,96,96,96,96,96,96,96,96,96,39,52,56,24,12,3,0,0,0,0,0,0,0,0,0,0,0,192,48,24,8,12,4,6,6,6,6,6,6,6,6,6,134,204,76,104,112,224,50,62,28,0,0],[0,0,0,0,0,0,127,24,24,24,24,24,24,24,24,31,25,24,24,24,24,24,24,24,24,24,126,0,0,0,0,0,0,0,0,0,0,0,224,56,28,12,12,12,12,24,48,224,192,192,224,96,96,112,48,48,56,24,30,0,0,0,0,0],[0,0,0,0,0,0,7,12,24,48,48,48,48,24,30,7,1,0,0,0,0,32,32,16,24,28,19,0,0,0,0,0,0,0,0,0,0,0,228,28,12,4,4,0,0,0,0,192,240,120,28,14,6,6,6,6,12,24,240,0,0,0,0,0],[0,0,0,0,0,0,63,49,33,65,65,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,7,0,0,0,0,0,0,0,0,0,0,0,252,132,134,130,130,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,224,0,0,0,0,0],[0,0,0,0,0,0,252,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,16,28,7,0,0,0,0,0,0,0,0,0,0,0,62,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,16,32,192,0,0,0,0,0],[0,0,0,0,0,0,124,24,24,24,12,12,12,12,6,6,6,7,3,3,3,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,31,4,4,4,8,8,8,8,16,16,16,48,32,32,32,192,192,192,192,128,128,0,0,0,0,0],[0,0,0,0,0,0,251,97,97,97,49,48,49,49,49,49,50,26,26,26,28,28,28,12,8,8,8,0,0,0,0,0,0,0,0,0,0,0,207,134,132,132,132,132,196,200,200,200,200,72,104,112,112,112,112,48,32,32,32,0,0,0,0,0],[0,0,0,0,0,0,126,24,28,12,12,14,6,7,3,3,1,1,2,2,4,4,8,8,16,16,124,0,0,0,0,0,0,0,0,0,0,0,124,16,16,32,32,64,64,128,128,128,128,192,192,224,96,96,48,48,24,24,62,0,0,0,0,0],[0,0,0,0,0,0,126,56,24,24,12,12,14,6,6,3,3,3,1,1,1,1,1,1,1,1,7,0,0,0,0,0,0,0,0,0,0,0,62,8,8,16,16,48,32,32,64,64,64,128,128,128,128,128,128,128,128,128,224,0,0,0,0,0],[0,0,0,0,0,0,31,24,16,32,32,0,0,0,1,1,3,3,7,6,14,12,28,24,56,48,127,0,0,0,0,0,0,0,0,0,0,0,252,24,24,48,112,96,224,192,192,128,128,0,0,0,0,0,4,4,8,24,248,0,0,0,0,0],[0,0,0,0,0,0,3,6,12,24,24,24,48,48,48,48,48,48,48,48,48,24,24,24,12,6,3,0,0,0,0,0,0,0,0,0,0,0,224,48,24,12,12,4,6,6,6,6,6,6,6,6,6,4,12,12,24,48,224,0,0,0,0,0],[0,0,0,0,0,0,0,1,31,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,31,0,0,0,0,0,0,0,0,0,0,0,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,128,192,248,0,0,0,0,0],[0,0,0,0,0,0,7,8,16,32,32,48,48,0,0,0,0,0,0,1,2,4,8,16,32,63,63,0,0,0,0,0,0,0,0,0,0,0,224,56,24,12,12,12,12,12,24,16,32,64,128,0,0,4,4,4,12,248,248,0,0,0,0,0],[0,0,0,0,0,0,7,24,48,48,48,48,0,0,0,3,0,0,0,0,0,48,48,48,48,24,7,0,0,0,0,0,0,0,0,0,0,0,192,112,48,24,24,24,24,48,96,192,112,24,8,12,12,12,12,8,24,48,192,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,1,1,2,6,4,8,8,16,32,32,127,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,48,112,112,240,112,112,112,112,112,112,112,112,112,112,254,112,112,112,112,112,112,254,0,0,0,0],[0,0,0,0,0,0,15,15,8,8,8,16,16,19,20,24,16,0,0,0,0,48,48,32,32,16,15,0,0,0,0,0,0,0,0,0,0,0,252,252,0,0,0,0,0,224,48,24,8,12,12,12,12,12,12,24,24,48,192,0,0,0,0,0],[0,0,0,0,0,0,1,3,4,8,24,24,16,48,49,54,60,56,48,48,48,48,24,24,12,6,3,0,0,0,0,0,0,0,0,0,0,0,240,8,12,12,0,0,0,0,240,24,12,6,6,6,6,6,6,4,12,24,224,0,0,0,0,0],[0,0,0,0,0,0,31,31,56,48,32,32,0,0,0,0,0,0,1,1,1,3,3,3,3,3,3,0,0,0,0,0,0,0,0,0,0,0,252,252,8,16,16,32,32,64,64,128,128,128,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,7,12,24,48,48,48,56,28,14,7,13,24,48,96,96,96,96,96,48,24,7,0,0,0,0,0,0,0,0,0,0,0,224,48,24,12,12,12,12,24,16,224,224,112,56,28,12,12,12,12,24,48,192,0,0,0,0,0],[0,0,0,0,0,0,7,24,48,48,96,96,96,96,96,96,48,24,15,0,0,0,0,48,48,48,15,0,0,0,0,0,0,0,0,0,0,0,192,32,16,24,8,12,12,12,12,28,60,108,140,12,24,24,24,48,96,192,128,0,0,0,0,0]]}}));r(i);i.font16x32,i.font12x24,i.font8x16;var a=o((function(t,r){var o=n&&n.__awaiter||function(t,e,n,r){return new(n||(n=Promise))((function(o,i){function a(t){try{s(r.next(t))}catch(t){i(t)}}function c(t){try{s(r.throw(t))}catch(t){i(t)}}function s(t){var e;t.done?o(t.value):(e=t.value,e instanceof n?e:new n((function(t){t(e)}))).then(a,c)}s((r=r.apply(t,e||[])).next())}))},a=n&&n.__generator||function(t,e){var n,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(i){return function(c){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return a.label++,{value:i[1],done:!1};case 5:a.label++,r=i[1],i=[0];continue;case 7:i=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){a=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]>16&255,green:n>>8&255,blue:255&n})},t.prototype.drawPointRGB=function(t,e,n){if(!(t>=this.w||e>=this.h||t<0||e<0)){var r=this.h-e-1,o=54+3*t+this._lineByteNum*r;this._data.writeUInt8(n.blue,o),this._data.writeUInt8(n.green,o+1),this._data.writeUInt8(n.red,o+2)}},t.prototype.getPointRGB=function(t,e){if(t>=this.w||e>=this.h||t<0||e<0)throw new Error("out of range");var n=this.h-e-1,r=54+3*t+this._lineByteNum*n;return{blue:this._data.readUInt8(r),green:this._data.readUInt8(r+1),red:this._data.readUInt8(r+2)}},t.prototype.drawLineH=function(t,e,n,r){if(t>e){var o=e;e=t,t=o}for(;t<=e;t++)this.drawPoint(t,n,r)},t.prototype.drawLineV=function(t,e,n,r){if(t>e){var o=e;e=t,t=o}for(;t<=e;t++)this.drawPoint(n,t,r)},t.prototype.drawLine=function(t,e,n,r,o){var i=t,a=e,c=n>t?n-t:t-n,s=r>e?r-e:e-r,d=!1,f=n>t?1:-1,u=r>e?1:-1;if(s>c){var h=c;c=s,s=h,d=!0}for(var l=(s<<1)-c,w=0;w<=c;w++)this.drawPoint(i,a,o),l>=0&&(d?i+=f:a+=u,l-=c<<1),d?a+=u:i+=f,l+=s<<1},t.prototype.drawRect=function(t,e,n,r,o){var i=t+n-1,a=e+r-1;this.drawLineH(t,i,e,o),this.drawLineH(t,i,a,o),this.drawLineV(e,a,t,o),this.drawLineV(e,a,i,o)},t.prototype.fillRect=function(t,e,n,r,o){var i=t+n-1,a=e+r-1;if(t>i){var c=i;i=t,t=c}if(e>a){c=a;a=e,e=c}for(;e<=a;e++)for(var s=t;s<=i;s++)this.drawPoint(s,e,o)},t.prototype.drawCircle=function(t,e,n,r){for(var o=0,i=n,a=3-2*n;o0;f<<=1)128&f&&this.drawPoint(c,a,o),c++;++a-n>=r.h&&(a=n,e+=8)}},t.prototype.drawString=function(t,e,n,r,o){for(var i=0,a=t;i(t.includes(r)&&n.push(e[r]),n),[]);return n[Math.random()*n.length|0]}function u(t={}){const e=["small","medium","big"],n={backgroundColor:16775912,size:4,noise:4,width:100,height:40,fontSize:["medium","big"]};let{backgroundColor:r,size:o,noise:i,width:a,height:s,fontSize:f}=Object.assign({},n,t);"string"==typeof r&&(r=r.replace("#","0x")),f instanceof Array||(f=[]),f.filter(t=>e.includes(t)),f.length||(f=n.fontSize),o=o>6?6:o;const u=new c(a,s);u.fillRect(0,0,a,s,r),function(t,e){for(let n=1;n>16,r=e>>8&255,o=255&e,i=Math.max(n,r,o),a=Math.min(n,r,o);return(i+a)/510}(t):1;let o,i;r>=.5?(o=Math.round(100*r)-45,i=Math.round(100*r)-25):(o=Math.round(100*r)+25,i=Math.round(100*r)+45);const a=h(o,i)/100,c=a<.5?a*(a+n):a+n-a*n,s=2*a-c,d=Math.floor(255*w(s,c,e+1/3)),f=Math.floor(255*w(s,c,e));return"#"+(Math.floor(255*w(s,c,e-1/3))|f<<8|d<<16|1<<24).toString(16).slice(1)};function w(t,e,n){return 6*(n=(n+1)%1)<1?t+(e-t)*n*6:2*n<1?e:3*n<2?t+(e-t)*(2/3-n)*6:t}const p=Object.prototype.toString;function g(t){return"[object Object]"===p.call(t)}function y(){"development"===process.env.NODE_ENV&&console.log(...arguments)}const v=async function(){};function m(t){return v.constructor===t.constructor?async function(){const e=await t.apply(this,arguments);return g(e)&&e.msg&&(e.message=e.msg),e}:function(){const e=t.apply(this,arguments);return g(e)&&e.msg&&(e.message=e.msg),e}}const b=uniCloud.database().collection("opendb-verify-codes"),_={};var I=Object.freeze({__proto__:null,create:async function(t={}){let{scene:e,expiresDate:n,deviceId:r,...o}=t;if(r=r||__ctx__.DEVICEID,!r)throw new Error("deviceId不可为空");if(!e)throw new Error("scene验证码场景不可为空");try{const{text:i,base64:a}=u(o),c=await this.setVerifyCode({deviceId:r,code:i,expiresDate:n,scene:e});return c.code>0?{...c,code:10001}:(_[r]=t,{code:0,msg:"验证码获取成功",captchaBase64:a})}catch(t){return{code:10001,msg:"验证码生成失败:"+t.message}}},verify:async function({deviceId:t,captcha:e,scene:n}){if(!(t=t||__ctx__.DEVICEID))throw new Error("deviceId不可为空");if(!n)throw new Error("scene验证码场景不可为空");try{const r=await this.verifyCode({deviceId:t,code:e,scene:n});return r.code>0?{...r,code:10002}:{code:0,msg:"验证码通过"}}catch(t){return{code:10002,msg:"验证码校验失败:"+t.message}}},refresh:async function(t={}){let{scene:e,expiresDate:n,deviceId:r,...o}=t;if(r=r||__ctx__.DEVICEID,!r)throw new Error("deviceId不可为空");if(!e)throw new Error("scene验证码场景不可为空");const i=await b.where({deviceId:r,scene:e,state:0}).orderBy("created_date","desc").limit(1).get();if(i&&i.data&&i.data.length>0){const t=i.data[0];await b.doc(t._id).update({state:2});let a={};Object.keys(o).length>0&&(_[r]=Object.assign({},_[r],o)),a=_[r];let c={};try{c=await this.create(Object.assign({},a,{deviceId:r,scene:e,expiresDate:n}))}catch(t){return{code:50403,msg:t.message}}return c.code>0?{...c,code:10003}:{code:0,msg:"验证码刷新成功",captchaBase64:c.captchaBase64}}return{code:10003,msg:"验证码刷新失败:无此设备信息,请重新获取"}},setVerifyCode:async function({deviceId:t,code:e,expiresDate:n,scene:r}){if(!t)return{code:10101,msg:"deviceId不可为空"};if(!e)return{code:10102,msg:"验证码不可为空"};n||(n=180);const o=Date.now(),i={deviceId:t,scene:r,code:e.toLocaleLowerCase(),state:0,ip:__ctx__.CLIENTIP,created_date:o,expired_date:o+1e3*n};return y("addRes",await b.add(i)),{code:0,deviceId:t}},verifyCode:async function({deviceId:t,code:e,scene:n}){if(!t)return{code:10101,msg:"deviceId不可为空"};if(!e)return{code:10102,msg:"验证码不可为空"};const r=Date.now(),o={deviceId:t,scene:n,code:e.toLocaleLowerCase(),state:0},i=await b.where(o).orderBy("created_date","desc").limit(1).get();if(y("verifyRecord:",i),i&&i.data&&i.data.length>0){const t=i.data[0];if(t.expired_date + + + + + + + + + {{ title }} + {{ extra }} + + + + + + + + + + + + + {{ title }} + {{ subTitle }} + + + + {{ extra }} + + + + + + + + + + + + {{ title }} + + + + + {{ extra }} + + + + + + + {{ note }} + + + + + + + + diff --git a/uni_modules/uni-card/package.json b/uni_modules/uni-card/package.json new file mode 100644 index 0000000..507273c --- /dev/null +++ b/uni_modules/uni-card/package.json @@ -0,0 +1,85 @@ +{ + "id": "uni-card", + "displayName": "uni-card 卡片", + "version": "1.2.1", + "description": "Card 组件,提供常见的卡片样式。", + "keywords": [ + "uni-ui", + "uniui", + "card", + "", + "卡片" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} diff --git a/uni_modules/uni-card/readme.md b/uni_modules/uni-card/readme.md new file mode 100644 index 0000000..6f72c83 --- /dev/null +++ b/uni_modules/uni-card/readme.md @@ -0,0 +1,104 @@ + + +## Card 卡片 +> **组件名:uni-card** +> 代码块: `uCard` + + +卡片视图组件。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 因为平台兼容问题 , 目前 APP-NVUE 安卓平台下不支持阴影 + + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + + 内容主体,可自定义内容及样式 + + + + + + + + + + uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可编译到iOS、Android、H5、以及各种小程序等多个平台。即使不跨端,uni-app同时也是更好的小程序开发框架。 + + + + + uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可编译到iOS、Android、H5、以及各种小程序等多个平台。即使不跨端,uni-app同时也是更好的小程序开发框架。 + + + + + 默认内容 + + +``` + +## API + +### Card Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|title |String |- |标题文字 | +|extra |String |- |标题额外信息 | +|note |String |- |底部信息 | +|thumbnail |String |- |标题左侧缩略图,支持网络图片,本地图片,本图片需要传入一个绝对路径,如:`/static/xxx.png` | +|mode |String |basic |卡片模式 ,可选值, basic:基础卡片 ;style :图文卡片 ; title :标题卡片 | +|isFull |Boolean|false |卡片内容是否通栏,为true时将去除padding值 | +|isShadow |Boolean|false |卡片内容是否开启阴影 | + + +### Card Events + +|事件称名 |事件说明 |返回参数 | +|:-: |:-: |:-: | +|@click |点击 Card 触发事件 |- | + + +### Card Slots + +|插槽称名 |说明 | +|:-: |:-: | +|header |卡片头部插槽( 图文卡片 mode="style" 时,不支持)| +|footer |卡片底部插槽 | + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/card/card](https://hellouniapp.dcloud.net.cn/pages/extUI/card/card) \ No newline at end of file diff --git a/uni_modules/uni-collapse/changelog.md b/uni_modules/uni-collapse/changelog.md new file mode 100644 index 0000000..9fb4b5c --- /dev/null +++ b/uni_modules/uni-collapse/changelog.md @@ -0,0 +1,27 @@ +## 1.3.3(2021-08-17) +- 优化 show-arrow 属性默认为true +## 1.3.2(2021-08-17) +- 新增 show-arrow 属性,控制是否显示右侧箭头 +## 1.3.1(2021-07-30) +- 优化 vue3下小程序事件警告的问题 +## 1.3.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.2.2(2021-07-21) +- 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug +## 1.2.1(2021-07-21) +- 优化 组件示例 +## 1.2.0(2021-07-21) +- 新增 组件折叠动画 +- 新增 value\v-model 属性 ,动态修改面板折叠状态 +- 新增 title 插槽 ,可定义面板标题 +- 新增 border 属性 ,显示隐藏面板内容分隔线 +- 新增 title-border 属性 ,显示隐藏面板标题分隔线 +- 修复 resize 方法失效的Bug +- 修复 change 事件返回参数不正确的Bug +- 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法 +## 1.1.7(2021-05-12) +- 新增 组件示例地址 +## 1.1.6(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.1.5(2021-02-05) +- 调整为uni_modules目录规范 \ No newline at end of file diff --git a/uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue b/uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue new file mode 100644 index 0000000..e962a9f --- /dev/null +++ b/uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue @@ -0,0 +1,402 @@ + + + + + diff --git a/uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue b/uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue new file mode 100644 index 0000000..b7360d4 --- /dev/null +++ b/uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue @@ -0,0 +1,146 @@ + + + diff --git a/uni_modules/uni-collapse/package.json b/uni_modules/uni-collapse/package.json new file mode 100644 index 0000000..965814f --- /dev/null +++ b/uni_modules/uni-collapse/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-collapse", + "displayName": "uni-collapse 折叠面板", + "version": "1.3.3", + "description": "Collapse 组件,可以折叠 / 展开的内容区域。", + "keywords": [ + "uni-ui", + "折叠", + "折叠面板", + "手风琴" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-collapse/readme.md b/uni_modules/uni-collapse/readme.md new file mode 100644 index 0000000..12e872f --- /dev/null +++ b/uni_modules/uni-collapse/readme.md @@ -0,0 +1,276 @@ + + +## Collapse 折叠面板 +> **组件名:uni-collapse** +> 代码块: `uCollapse` +> 关联组件:`uni-collapse-item`、`uni-icons`。 + + +折叠面板用来折叠/显示过长的内容或者是列表。通常是在多内容分类项使用,折叠不重要的内容,显示重要内容。点击可以展开折叠部分。 + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - `App` 端默认关闭组件动画 ,因为 `height` 动画开销比较大,会导致页面卡顿,请酌情使用动画 +> - 如在使用组件过程从发现卡顿严重,请尝试停用组件动画,问题原因如上 +> - 在小程序端组件内容发生变化,需要手动调用 resize() 方法,手动更新几点信息,避免出现内容错位 +> - 如需自定义组件默认边框颜色等,请使用插槽自定义内容并合理使用 `border ` 和 `title-border` 属性 +> - 折叠面板仅支持嵌套使用,请勿单独使用 +> - 组件支持 nvue ,需要在 `manifest.json > app-plus` 节点下配置 `"nvueStyleCompiler" : "uni-app"` +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + + +### 基本用法 + +使用 `title` 属性指定面板显示内容 + +使用 `open` 属性默认打开当前面板 + +使用 `disabled` 属性禁用面板 + + +```html + + + 折叠内容 + + + 折叠内容 + + + 折叠内容 + + +``` + +### 手风琴效果 + +使用 `accordion` 属性,可以仅打开一个面板并关闭其他已经打开的面板,效果类似手风琴 + +设置 `accordion` 属性时,`open` 属性则生效在最后一个组件 + +```html + + + 折叠内容 + + + 折叠内容 + + + 折叠内容 + + +``` + +### 动态设置折叠面板打开状态 + +使用 `v-model` 属性,动态设置面板的显示状态 + +使用 `name` 属性设置每个面板的唯一标识,如不设置使用默认索引,从字符串 `"0"` 开始记数 + +**注意** + +- 如果 `accordion` 属性为 `true` 则 `v-model` 类型为 `String` +- 如果 `accordion` 属性为 `false` 则 `v-model` 类型为 `Array` +- 请注意 `v-model` 属性与 `open` 属性请勿一起使用 ,建议只使用 `v-model` + +```html + + + 折叠内容 + + + 折叠内容 + + + 折叠内容 + + +``` + +```javascript +export default { + data(){ + return { + value:['key1','key2'], + // 如果设置了 accordion 属性,则使用 string 类型 + // value:'key1' + } + } +} +``` + +### 使用动画 + +使用 `show-animation` 属性开启或关闭面板折叠动画,默认动画开启 + +**注意** + +- `App` 端默认关闭组件动画 ,因为 height 动画开销比较大,会导致页面卡顿,请酌情使用动画,如出现明显卡顿,尝试关闭动画 + + +```html + + + 折叠内容 + + + 折叠内容 + + + 折叠内容 + + +``` + +### 配置图片 + +使用 `thumb` 配置图片地址, 可在面板左侧显示一个图片 + +如需显示更多内容,如图标等,请见下方自定义插槽的说明 + +```html + + + + 折叠内容主体,可自定义内容及样式 + + + +``` + +### 自定义插槽 + +如果需要自定义面板显示,可以使用 `title` 插槽达成完全自定义。下面是一个使用 `uni-list` 的列表示例,需要引入 `uni-list` 组件 + +```html + + + + + + 折叠内容主体,可自定义内容及样式 + + + +``` + +**注意** + +- 在折叠面板组件中使用list时,在 App-Nvue 下请勿单独使用 uni-list-item,会导致组件无法正常显示,其他平台不做限制 +- 在默认插槽里使用 uni-list 组件与上方示例一样,直接写在默认插槽里即可 + +## API + +### Collapse Props + +|属性名|类型|默认值|说明| +|:-:|:-:|:-:|:-:| +|value/v-model|String/Array|-|当前激活面板改变时触发(如果是手风琴模式,参数类型为string,否则为array)| +|accordion|Boolean|false|是否开启手风琴效果 | + +### Collapse Event + +|事件称名|说明|返回值| +|:-:|:-:|:-:| +|@change|切换面板时触发 |切换面板时触发,如果是手风琴模式,返回类型为string,否则为array| + +### Collapse Methods + +|方法名称|说明| +|:-:|:-:| +|resize |更新当前列表高度| + +> **提示** +> - resize 方法解决动态添加数据,带动画的折叠面板高度不更新的问题 +> - 需要在数据渲染完毕之后使用 `resize` 方法。推荐在 `this.$nextTick()` 中使用 +> - 当前只有小程序端需要调用此方法,H5\App 端已经做了处理,不需要手动更新高度 +> ```html +> +> +> +> +> {{content}} +> +> +> +> +> 折叠内容主体,这是一段比较长内容。默认折叠主要内容,只显示当前项标题。点击标题展开,才能看到这段文字。再次点击标题,折叠内容。 +> +> +> +> +> +> ``` +> ```javascript +> export default { +> data() { +> return { +> value:['0'], +> content: '折叠内容主体,可自定义内容及样式,点击按钮修改内容使高度发生变化。', +> } +> }, +> methods: { +> add() { +> if (this.content.length > 35) { +> this.content = '折叠内容主体,可自定义内容及样式,点击按钮修改内容使高度发生变化。' +> } else { +> this.content = '折叠内容主体,这是一段比较长内容。通过点击按钮修改后内容后,使组件高度发生变化,在次点击按钮恢复之前的内容和高度。' +> } +> // TODO 小程序中不支持自动更新 ,需要手动resize 更新组件高度 +> // #ifdef MP +> this.$nextTick(() => { +> this.$refs.collapse.resize() +> }) +> // #endif +> } +> } +> } +> ``` + + +### CollapseItem Props + +|属性名|类型|默认值|说明| +|:-:|:-:|:-:|:-:| +|title|String|-|标题文字| +|thumb|String|-|标题左侧缩略图| +|disabled|Boolean|false|是否禁用| +|open|Boolean|false|是否展开面板| +|show-animation|Boolean|false|开启动画| +|border|Boolean|true|折叠面板内容分隔线| +|title-border|String|auto|折叠面板标题分隔线可选值见下方 **TitleBorder Params**| +|show-arrow|Boolean|true|是否显示右侧箭头| + +#### TitleBorder Params + +|参数名|说明| +|:-:|:-:| +|auto|分隔线自动显示| +|none|不显示分隔线| +|show|一直显示分隔线| + +### Collapse Slots + +|插槽名|说明| +|:-:| :-:| +|default|默认插槽| +|title|面板标题插槽,如使用此插槽禁用样式效果将失效| + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/collapse/collapse](https://hellouniapp.dcloud.net.cn/pages/extUI/collapse/collapse) \ No newline at end of file diff --git a/uni_modules/uni-combox/changelog.md b/uni_modules/uni-combox/changelog.md new file mode 100644 index 0000000..39e8a05 --- /dev/null +++ b/uni_modules/uni-combox/changelog.md @@ -0,0 +1,10 @@ +## 0.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.0.6(2021-05-12) +- 新增 组件示例地址 +## 0.0.5(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 0.0.4(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 0.0.3(2021-02-04) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-combox/components/uni-combox/uni-combox.vue b/uni_modules/uni-combox/components/uni-combox/uni-combox.vue new file mode 100644 index 0000000..fef6111 --- /dev/null +++ b/uni_modules/uni-combox/components/uni-combox/uni-combox.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/uni_modules/uni-combox/package.json b/uni_modules/uni-combox/package.json new file mode 100644 index 0000000..1254459 --- /dev/null +++ b/uni_modules/uni-combox/package.json @@ -0,0 +1,85 @@ +{ + "id": "uni-combox", + "displayName": "uni-combox 组合框", + "version": "0.1.0", + "description": "可以选择也可以输入的表单项 ", + "keywords": [ + "uni-ui", + "uniui", + "combox", + "组合框", + "select" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-combox/readme.md b/uni_modules/uni-combox/readme.md new file mode 100644 index 0000000..edab4d4 --- /dev/null +++ b/uni_modules/uni-combox/readme.md @@ -0,0 +1,52 @@ + + +## Combox 组合框 +> **组件名:uni-combox** +> 代码块: `uCombox` + + +组合框组件。 + +### 平台兼容性说明 + +**暂不支持nvue** + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 +```html + +``` + +## API + +### Combox Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|label |String |- |标签文字 | +|value |String |- |combox的值 | +|labelWidth |String |auto |标签宽度,有单位字符串,如:'100px' | +|placeholder|String |- |输入框占位符 | +|candidates |Array/String |[] |候选字段 | +|emptyTips |String |无匹配项 |无匹配项时的提示语 | + +### Combox Events + +|事件称名 |说明 |返回值 | +|:-: |:-: |:-: | +|@input |combox输入事件 |返回combox值| + + + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/combox/combox](https://hellouniapp.dcloud.net.cn/pages/extUI/combox/combox) \ No newline at end of file diff --git a/uni_modules/uni-config-center/changelog.md b/uni_modules/uni-config-center/changelog.md new file mode 100644 index 0000000..4d2eb92 --- /dev/null +++ b/uni_modules/uni-config-center/changelog.md @@ -0,0 +1,4 @@ +## 0.0.2(2021-04-16) +- 修改插件package信息 +## 0.0.1(2021-03-15) +- 初始化项目 diff --git a/uni_modules/uni-config-center/package.json b/uni_modules/uni-config-center/package.json new file mode 100644 index 0000000..c5dec93 --- /dev/null +++ b/uni_modules/uni-config-center/package.json @@ -0,0 +1,80 @@ +{ + "id": "uni-config-center", + "displayName": "uni-config-center", + "version": "0.0.2", + "description": "uniCloud 配置中心", + "keywords": [ + "配置", + "配置中心" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "uniCloud", + "云函数模板" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "directories": { + "example": "../../../scripts/dist" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "u", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "u", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "u", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} diff --git a/uni_modules/uni-config-center/readme.md b/uni_modules/uni-config-center/readme.md new file mode 100644 index 0000000..03f7fc2 --- /dev/null +++ b/uni_modules/uni-config-center/readme.md @@ -0,0 +1,93 @@ +# 为什么使用uni-config-center + +实际开发中很多插件需要配置文件才可以正常运行,如果每个插件都单独进行配置的话就会产生下面这样的目录结构 + +```bash +cloudfunctions +└─────common 公共模块 + ├─plugin-a // 插件A对应的目录 + │ ├─index.js + │ ├─config.json // plugin-a对应的配置文件 + │ └─other-file.cert // plugin-a依赖的其他文件 + └─plugin-b // plugin-b对应的目录 + ├─index.js + └─config.json // plugin-b对应的配置文件 +``` + +假设插件作者要发布一个项目模板,里面使用了很多需要配置的插件,无论是作者发布还是用户使用都是一个大麻烦。 + +uni-config-center就是用了统一管理这些配置文件的,使用uni-config-center后的目录结构如下 + +```bash +cloudfunctions +└─────common 公共模块 + ├─plugin-a // 插件A对应的目录 + │ └─index.js + ├─plugin-b // plugin-b对应的目录 + │ └─index.js + └─uni-config-center + ├─index.js // config-center入口文件 + ├─plugin-a + │ ├─config.json // plugin-a对应的配置文件 + │ └─other-file.cert // plugin-a依赖的其他文件 + └─plugin-b + └─config.json // plugin-b对应的配置文件 +``` + +使用uni-config-center后的优势 + +- 配置文件统一管理,分离插件主体和配置信息,更新插件更方便 +- 支持对config.json设置schema,插件使用者在HBuilderX内编写config.json文件时会有更好的提示(后续HBuilderX会提供支持) + +# 用法 + +在要使用uni-config-center的公共模块或云函数内引入uni-config-center依赖,请参考:[使用公共模块](https://uniapp.dcloud.net.cn/uniCloud/cf-common) + +```js +const createConfig = require('uni-config-center') + +const uniIdConfig = createConfig({ + pluginId: 'uni-id', // 插件id + defaultConfig: { // 默认配置 + tokenExpiresIn: 7200, + tokenExpiresThreshold: 600, + }, + customMerge: function(defaultConfig, userConfig) { // 自定义默认配置和用户配置的合并规则,不设置的情况侠会对默认配置和用户配置进行深度合并 + // defaudltConfig 默认配置 + // userConfig 用户配置 + return Object.assign(defaultConfig, userConfig) + } +}) + + +// 以如下配置为例 +// { +// "tokenExpiresIn": 7200, +// "passwordErrorLimit": 6, +// "bindTokenToDevice": false, +// "passwordErrorRetryTime": 3600, +// "app-plus": { +// "tokenExpiresIn": 2592000 +// }, +// "service": { +// "sms": { +// "codeExpiresIn": 300 +// } +// } +// } + +// 获取配置 +uniIdConfig.config() // 获取全部配置,注意:uni-config-center内不存在对应插件目录时会返回空对象 +uniIdConfig.config('tokenExpiresIn') // 指定键值获取配置,返回:7200 +uniIdConfig.config('service.sms.codeExpiresIn') // 指定键值获取配置,返回:300 +uniIdConfig.config('tokenExpiresThreshold', 600) // 指定键值获取配置,如果不存在则取传入的默认值,返回:600 + +// 获取文件绝对路径 +uniIdConfig.resolve('custom-token.js') // 获取uni-config-center/uni-id/custom-token.js文件的路径 + +// 引用文件(require) +uniIDConfig.requireFile('custom-token.js') // 使用require方式引用uni-config-center/uni-id/custom-token.js文件。文件不存在时返回undefined,文件内有其他错误导致require失败时会抛出错误。 + +// 判断是否包含某文件 +uniIDConfig.hasFile('custom-token.js') // 配置目录是否包含某文件,true: 文件存在,false: 文件不存在 +``` \ No newline at end of file diff --git a/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/index.js b/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/index.js new file mode 100644 index 0000000..e14fb3b --- /dev/null +++ b/uni_modules/uni-config-center/uniCloud/cloudfunctions/common/uni-config-center/index.js @@ -0,0 +1 @@ +"use strict";var t=require("fs"),r=require("path");function e(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var n=e(t),o=e(r),i="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};var u=function(t){var r={exports:{}};return t(r,r.exports),r.exports}((function(t,r){var e="__lodash_hash_undefined__",n=9007199254740991,o="[object Arguments]",u="[object Function]",c="[object Object]",a=/^\[object .+?Constructor\]$/,f=/^(?:0|[1-9]\d*)$/,s={};s["[object Float32Array]"]=s["[object Float64Array]"]=s["[object Int8Array]"]=s["[object Int16Array]"]=s["[object Int32Array]"]=s["[object Uint8Array]"]=s["[object Uint8ClampedArray]"]=s["[object Uint16Array]"]=s["[object Uint32Array]"]=!0,s[o]=s["[object Array]"]=s["[object ArrayBuffer]"]=s["[object Boolean]"]=s["[object DataView]"]=s["[object Date]"]=s["[object Error]"]=s[u]=s["[object Map]"]=s["[object Number]"]=s[c]=s["[object RegExp]"]=s["[object Set]"]=s["[object String]"]=s["[object WeakMap]"]=!1;var l="object"==typeof i&&i&&i.Object===Object&&i,h="object"==typeof self&&self&&self.Object===Object&&self,p=l||h||Function("return this")(),_=r&&!r.nodeType&&r,v=_&&t&&!t.nodeType&&t,d=v&&v.exports===_,y=d&&l.process,g=function(){try{var t=v&&v.require&&v.require("util").types;return t||y&&y.binding&&y.binding("util")}catch(t){}}(),b=g&&g.isTypedArray;function j(t,r,e){switch(e.length){case 0:return t.call(r);case 1:return t.call(r,e[0]);case 2:return t.call(r,e[0],e[1]);case 3:return t.call(r,e[0],e[1],e[2])}return t.apply(r,e)}var w,O,m,A=Array.prototype,z=Function.prototype,M=Object.prototype,x=p["__core-js_shared__"],C=z.toString,F=M.hasOwnProperty,U=(w=/[^.]+$/.exec(x&&x.keys&&x.keys.IE_PROTO||""))?"Symbol(src)_1."+w:"",S=M.toString,I=C.call(Object),P=RegExp("^"+C.call(F).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),T=d?p.Buffer:void 0,q=p.Symbol,E=p.Uint8Array,$=T?T.allocUnsafe:void 0,D=(O=Object.getPrototypeOf,m=Object,function(t){return O(m(t))}),k=Object.create,B=M.propertyIsEnumerable,N=A.splice,L=q?q.toStringTag:void 0,R=function(){try{var t=_t(Object,"defineProperty");return t({},"",{}),t}catch(t){}}(),G=T?T.isBuffer:void 0,V=Math.max,W=Date.now,H=_t(p,"Map"),J=_t(Object,"create"),K=function(){function t(){}return function(r){if(!Mt(r))return{};if(k)return k(r);t.prototype=r;var e=new t;return t.prototype=void 0,e}}();function Q(t){var r=-1,e=null==t?0:t.length;for(this.clear();++r-1},X.prototype.set=function(t,r){var e=this.__data__,n=nt(e,t);return n<0?(++this.size,e.push([t,r])):e[n][1]=r,this},Y.prototype.clear=function(){this.size=0,this.__data__={hash:new Q,map:new(H||X),string:new Q}},Y.prototype.delete=function(t){var r=pt(this,t).delete(t);return this.size-=r?1:0,r},Y.prototype.get=function(t){return pt(this,t).get(t)},Y.prototype.has=function(t){return pt(this,t).has(t)},Y.prototype.set=function(t,r){var e=pt(this,t),n=e.size;return e.set(t,r),this.size+=e.size==n?0:1,this},Z.prototype.clear=function(){this.__data__=new X,this.size=0},Z.prototype.delete=function(t){var r=this.__data__,e=r.delete(t);return this.size=r.size,e},Z.prototype.get=function(t){return this.__data__.get(t)},Z.prototype.has=function(t){return this.__data__.has(t)},Z.prototype.set=function(t,r){var e=this.__data__;if(e instanceof X){var n=e.__data__;if(!H||n.length<199)return n.push([t,r]),this.size=++e.size,this;e=this.__data__=new Y(n)}return e.set(t,r),this.size=e.size,this};var it,ut=function(t,r,e){for(var n=-1,o=Object(t),i=e(t),u=i.length;u--;){var c=i[it?u:++n];if(!1===r(o[c],c,o))break}return t};function ct(t){return null==t?void 0===t?"[object Undefined]":"[object Null]":L&&L in Object(t)?function(t){var r=F.call(t,L),e=t[L];try{t[L]=void 0;var n=!0}catch(t){}var o=S.call(t);n&&(r?t[L]=e:delete t[L]);return o}(t):function(t){return S.call(t)}(t)}function at(t){return xt(t)&&ct(t)==o}function ft(t){return!(!Mt(t)||function(t){return!!U&&U in t}(t))&&(At(t)?P:a).test(function(t){if(null!=t){try{return C.call(t)}catch(t){}try{return t+""}catch(t){}}return""}(t))}function st(t){if(!Mt(t))return function(t){var r=[];if(null!=t)for(var e in Object(t))r.push(e);return r}(t);var r=dt(t),e=[];for(var n in t)("constructor"!=n||!r&&F.call(t,n))&&e.push(n);return e}function lt(t,r,e,n,o){t!==r&&ut(r,(function(i,u){if(o||(o=new Z),Mt(i))!function(t,r,e,n,o,i,u){var a=yt(t,e),f=yt(r,e),s=u.get(f);if(s)return void rt(t,e,s);var l=i?i(a,f,e+"",t,r,u):void 0,h=void 0===l;if(h){var p=wt(f),_=!p&&mt(f),v=!p&&!_&&Ct(f);l=f,p||_||v?wt(a)?l=a:xt(j=a)&&Ot(j)?l=function(t,r){var e=-1,n=t.length;r||(r=Array(n));for(;++e-1&&t%1==0&&t0){if(++r>=800)return arguments[0]}else r=0;return t.apply(void 0,arguments)}}(R?function(t,r){return R(t,"toString",{configurable:!0,enumerable:!1,value:(e=r,function(){return e}),writable:!0});var e}:It);function bt(t,r){return t===r||t!=t&&r!=r}var jt=at(function(){return arguments}())?at:function(t){return xt(t)&&F.call(t,"callee")&&!B.call(t,"callee")},wt=Array.isArray;function Ot(t){return null!=t&&zt(t.length)&&!At(t)}var mt=G||function(){return!1};function At(t){if(!Mt(t))return!1;var r=ct(t);return r==u||"[object GeneratorFunction]"==r||"[object AsyncFunction]"==r||"[object Proxy]"==r}function zt(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=n}function Mt(t){var r=typeof t;return null!=t&&("object"==r||"function"==r)}function xt(t){return null!=t&&"object"==typeof t}var Ct=b?function(t){return function(r){return t(r)}}(b):function(t){return xt(t)&&zt(t.length)&&!!s[ct(t)]};function Ft(t){return Ot(t)?tt(t,!0):st(t)}var Ut,St=(Ut=function(t,r,e){lt(t,r,e)},ht((function(t,r){var e=-1,n=r.length,o=n>1?r[n-1]:void 0,i=n>2?r[2]:void 0;for(o=Ut.length>3&&"function"==typeof o?(n--,o):void 0,i&&function(t,r,e){if(!Mt(e))return!1;var n=typeof r;return!!("number"==n?Ot(e)&&vt(r,e.length):"string"==n&&r in e)&&bt(e[r],t)}(r[0],r[1],i)&&(o=n<3?void 0:o,n=1),t=Object(t);++ec.call(t,r);class f{constructor({pluginId:t,defaultConfig:r={},customMerge:e,root:n}){this.pluginId=t,this.defaultConfig=r,this.pluginConfigPath=o.default.resolve(n||__dirname,t),this.customMerge=e,this._config=void 0}resolve(t){return o.default.resolve(this.pluginConfigPath,t)}hasFile(t){return n.default.existsSync(this.resolve(t))}requireFile(t){try{return require(this.resolve(t))}catch(t){if("MODULE_NOT_FOUND"===t.code)return;throw t}}_getUserConfig(){return this.requireFile("config.json")}config(t,r){this._config||(this._config=(this.customMerge||u)(this.defaultConfig,this._getUserConfig()));let e=this._config;return t?function(t,r,e){if("number"==typeof r)return t[r];if("symbol"==typeof r)return a(t,r)?t[r]:e;const n="string"!=typeof(o=r)?o:o.split(".").reduce(((t,r)=>(r.split(/\[([^}]+)\]/g).forEach((r=>r&&t.push(r))),t)),[]);var o;let i=t;for(let t=0;t + + {{ d }} + {{dayText}} + {{ h }} + {{ showColon ? ':' : hourText }} + {{ i }} + {{ showColon ? ':' : minuteText }} + {{ s }} + {{secondText}} + + + + diff --git a/uni_modules/uni-countdown/package.json b/uni_modules/uni-countdown/package.json new file mode 100644 index 0000000..7960b62 --- /dev/null +++ b/uni_modules/uni-countdown/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-countdown", + "displayName": "uni-countdown 倒计时", + "version": "1.1.2", + "description": "CountDown 倒计时组件", + "keywords": [ + "uni-ui", + "uniui", + "countdown", + "倒计时" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-countdown/readme.md b/uni_modules/uni-countdown/readme.md new file mode 100644 index 0000000..25c2a3a --- /dev/null +++ b/uni_modules/uni-countdown/readme.md @@ -0,0 +1,57 @@ + + +## CountDown 倒计时 +> **组件名:uni-countdown** +> 代码块: `uCountDown` + + +倒计时组件。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + + + + + + + +``` + +## API + +### Countdown Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|backgroundColor |String |#FFFFFF|背景色 | +|color |String |#000000|文字颜色 | +|splitorColor |String |#000000|分割符号颜色 | +|day |Number |0 |天数 | +|hour |Number |0 |小时 | +|minute |Number |0 |分钟 | +|second |Number |0 |秒 | +|showDay |Boolean|true |是否显示天数 | +|showColon |Boolean|true |是否以冒号为分隔符 | +|start |Boolean|true |是否初始化组件后就开始倒计时| + +### Countdown Events + +|事件称名 |说明 |返回值 | +|:-: |:-: |:-: | +|@timeup|倒计时时间到触发事件 |- | + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/countdown/countdown](https://hellouniapp.dcloud.net.cn/pages/extUI/countdown/countdown) \ No newline at end of file diff --git a/uni_modules/uni-data-checkbox/changelog.md b/uni_modules/uni-data-checkbox/changelog.md new file mode 100644 index 0000000..cda4c3b --- /dev/null +++ b/uni_modules/uni-data-checkbox/changelog.md @@ -0,0 +1,36 @@ +## 0.2.5(2021-08-23) +- 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题 +## 0.2.4(2021-08-17) +- 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题 +## 0.2.3(2021-08-11) +- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 +## 0.2.2(2021-07-30) +- 优化 在uni-forms组件,与label不对齐的问题 +## 0.2.1(2021-07-27) +- 修复 单选默认值为0不能选中的Bug +## 0.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.1.11(2021-07-06) +- 优化 删除无用日志 +## 0.1.10(2021-07-05) +- 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题 +## 0.1.9(2021-07-05) +- 修复 nvue 黑框样式问题 +## 0.1.8(2021-06-28) +- 修复 selectedTextColor 属性不生效的Bug +## 0.1.7(2021-06-02) +- 新增 map 属性,可以方便映射text/value属性 +## 0.1.6(2021-05-26) +- 修复 不关联服务空间的情况下组件报错的Bug +## 0.1.5(2021-05-12) +- 新增 组件示例地址 +## 0.1.4(2021-04-09) +- 修复 nvue 下无法选中的问题 +## 0.1.3(2021-03-22) +- 新增 disabled属性 +## 0.1.2(2021-02-24) +- 优化 默认颜色显示 +## 0.1.1(2021-02-24) +- 新增 支持nvue +## 0.1.0(2021-02-18) +- “暂无数据”显示居中 diff --git a/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue b/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue new file mode 100644 index 0000000..71dd59b --- /dev/null +++ b/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue @@ -0,0 +1,823 @@ + + + + + diff --git a/uni_modules/uni-data-checkbox/package.json b/uni_modules/uni-data-checkbox/package.json new file mode 100644 index 0000000..e978c4e --- /dev/null +++ b/uni_modules/uni-data-checkbox/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-data-checkbox", + "displayName": "uni-data-checkbox 数据选择器", + "version": "0.2.5", + "description": "通过数据驱动的单选框和复选框", + "keywords": [ + "uni-ui", + "checkbox", + "单选", + "多选", + "单选多选" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.1" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-load-more"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} diff --git a/uni_modules/uni-data-checkbox/readme.md b/uni_modules/uni-data-checkbox/readme.md new file mode 100644 index 0000000..4850085 --- /dev/null +++ b/uni_modules/uni-data-checkbox/readme.md @@ -0,0 +1,299 @@ + + +## DataCheckbox 数据驱动的单选复选框 +> **组件名:uni-data-checkbox** +> 代码块: `uDataCheckbox` + + +本组件是基于uni-app基础组件checkbox的封装。本组件要解决问题包括: + +1. 数据绑定型组件:给本组件绑定一个data,会自动渲染一组候选内容。再以往,开发者需要编写不少代码实现类似功能 +2. 自动的表单校验:组件绑定了data,且符合[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)组件的表单校验规范,搭配使用会自动实现表单校验 +3. 本组件合并了单选多选 +4. 本组件有若干风格选择,如普通的单选多选框、并列button风格、tag风格。开发者可以快速选择需要的风格。但作为一个封装组件,样式代码虽然不用自己写了,却会牺牲一定的样式自定义性 + +在uniCloud开发中,`DB Schema`中配置了enum枚举等类型后,在web控制台的[自动生成表单](https://uniapp.dcloud.io/uniCloud/schema?id=autocode)功能中,会自动生成``uni-data-checkbox``组件并绑定好data + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - 本组件为数据驱动,目的是快速投入使用,只可通过 style 覆盖有限样式,不支持自定义更多样式 +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 +> - 组件支持 nvue ,需要在 `manifest.json > app-plus` 节点下配置 `"nvueStyleCompiler" : "uni-app"` +> - 如组件显示有问题 ,请升级 `HBuilderX` 为 `v3.1.0` 以上 + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另行文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +设置 `localdata` 属性后,组件会通过数据渲染出对应的内容,默认显示的是单选框 + +需要注意 `:multiple="false"` 时(单选) , `value/v-model` 的值是 `String|Number` 类型 + +```html + + +``` + +```javascript + +export default { + data() { + return { + value: 0, + range: [{"value": 0,"text": "篮球" },{"value": 1,"text": "足球"},{"value": 2,"text": "游泳"}] + } + }, + methods: { + change(e){ + console.log('e:',e); + } + } +} +``` + +### 多选框 + +设置 `multiple` 属性,组件显示为多选框 + +需要注意 `:multiple="true"` 时(多选) , `value/v-model` 的值是 `Array` 类型 + +```html + + +``` + +```javascript + +export default { + data() { + return { + value: [0,2], + range: [{"value": 0,"text": "篮球" },{"value": 1,"text": "足球"},{"value": 2,"text": "游泳"}] + } + }, + methods: { + change(e){ + console.log('e:',e); + } + } +} +``` + +### 设置最大最小值 + +设置 `:multiple="true"` 时(多选) ,可以设置 `min`、`max` 属性 + +如果选中个数小于 `min` 属性设置的值,那么选中内容将不可取消,只有当选中个数大于等于 `min`且小于 `max` 时,才可取消选中 + +如果选中个数大于等于 `max` 属性设置的值,那么其他未选中内容将不可选 + +```html + + +``` + +```javascript + +export default { + data() { + return { + value: [0,2], + range: [{"value": 0,"text": "篮球" },{"value": 1,"text": "足球"},{"value": 2,"text": "游泳"}] + } + }, + methods: { + change(e){ + console.log('e:',e); + } + } +} +``` + +### 设置禁用 + +如果需要禁用某项,需要在 `localdata` 属性的数据源中添加 `disable` 属性,而不是在组件中添加 `disable` 属性 + +```html + + +``` + +```javascript + +export default { + data() { + return { + value: 0, + range: [{ + "value": 0, + "text": "篮球" + }, + { + "value": 1, + "text": "足球", + // 禁用当前项 + "disable":true + }, + { + "value": 2, + "text": "游泳" + } + ] + } + }, + methods: { + change(e){ + console.log('e:',e); + } + } +} +``` + + +### 自定义选中颜色 + +设置 `selectedColor` 属性,可以修改组件选中后的图标及边框颜色 + +设置 `selectedTextColor` 属性,可以修改组件选中后的文字颜色,如不填写默认同 `selectedColor` 属性 ,`mode` 属性为 `tag` 时,默认为白色 + +```html + + +``` + +```javascript + +export default { + data() { + return { + value: [0,2], + range: [{"value": 0,"text": "篮球" },{"value": 1,"text": "足球"},{"value": 2,"text": "游泳"}] + } + }, + methods: { + change(e){ + console.log('e:',e); + } + } +} +``` + +### 更多模式 + +设置 `mode` 属性,可以设置更多显示样式,目前内置样式有四种 `default/list/button/tag` + +如果需要禁用某项,需要在 `localdata` 属性的数据源中添加 `disable` 属性,而不是在组件中添加 `disable` 属性 + +```html + + +``` + +```javascript + +export default { + data() { + return { + value: 0, + range: [{"value": 0,"text": "篮球" },{"value": 1,"text": "足球"},{"value": 2,"text": "游泳"}] + } + }, + methods: { + change(e){ + console.log('e:',e); + } + } +} +``` + + +## API + +### DataCheckbox Props + +| 属性名 | 类型 |可选值 | 默认值| 说明 | +| :-: | :-: |:-: |:-: | :-: | +|value/v-model|Array/String/Number|- |- |默认值,multiple=true时为 Array类型,否则为 String或Number类型 | +|localdata |Array |- |- |本地渲染数据, | +|mode | String |default/list/button/tag|default|显示模式 | +|multiple |Boolean |- |false |是否多选 | +|min |String/Number |- |- |最小选择个数 ,multiple为true时生效 | +|max |String/Number |- |- |最大选择个数 ,multiple为true时生效 | +|wrap |Boolean |- |- |是否换行显示 | +|icon |String |left/right |left |list 列表模式下 icon 显示的位置 | +|selectedColor|String |- |#007aff|选中颜色| +|selectedTextColor|String |- |#333 |选中文本颜色,如不填写则自动显示| +|emptyText |String |- |暂无数据 |没有数据时显示的文字 ,本地数据无效| +|map |Object |- |{text:'text',value:'value'} |字段映射,将text/value映射到数据中的其他字段| + +#### Localdata Options + +`localdata` 属性的格式为数组,数组内每项是对象,需要严格遵循如下格式 + +|属性名 | 说明 | +|:-: | :-: | +|text |显示文本 | +|value |选中后的值 | +|disable |是否禁用 | + +#### Mode Options + +|属性名 | 说明 | +|:-: | :-: | +|default |默认值,横向显示 | +|list |列表 | +|button |按钮 | +|tag |标签 | + + +### DataCheckbox Events + +| 事件名 | 事件说明 | 返回参数| +| :-: | :-: | :-: | +| @change| 选中状态改变时触发事件 | - | + + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/data-checkbox/data-checkbox](https://hellouniapp.dcloud.net.cn/pages/extUI/data-checkbox/data-checkbox) \ No newline at end of file diff --git a/uni_modules/uni-data-picker/changelog.md b/uni_modules/uni-data-picker/changelog.md new file mode 100644 index 0000000..269cedc --- /dev/null +++ b/uni_modules/uni-data-picker/changelog.md @@ -0,0 +1,25 @@ +## 0.4.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.3.5(2021-06-04) +- 修复 无法加载云端数据的问题 +## 0.3.4(2021-05-28) +- 修复 v-model无效问题 +- 修复 loaddata 为空数据组时加载时间过长问题 +- 修复 上个版本引出的本地数据无法选择带有children的2级节点 +## 0.3.3(2021-05-12) +- 新增 组件示例地址 +## 0.3.2(2021-04-22) +- 修复 非树形数据有 where 属性查询报错的问题 +## 0.3.1(2021-04-15) +- 修复 本地数据概率无法回显时问题 +## 0.3.0(2021-04-07) +- 新增 支持云端非树形表结构数据 +- 修复 根节点 parent_field 字段等于null时选择界面错乱问题 +## 0.2.0(2021-03-15) +- 修复 nodeclick、popupopened、popupclosed事件无法触发的问题 +## 0.1.9(2021-03-09) +- 修复 微信小程序某些情况下无法选择的问题 +## 0.1.8(2021-02-05) +- 优化 部分样式在nvue上的兼容表现 +## 0.1.7(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-data-picker/components/uni-data-picker/config.json b/uni_modules/uni-data-picker/components/uni-data-picker/config.json new file mode 100644 index 0000000..dc6b403 --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-picker/config.json @@ -0,0 +1,12 @@ +{ + "id": "3796", + "name": "DataPicker", + "desc": "数据驱动的picker选择器", + "url": "data-picker", + "type": "表单组件", + "edition": "0.0.8", + "suffix": "vue", + "module": ["uni-data-picker","uni-data-pickerview","uni-load-more"], + "path": "https://ext.dcloud.net.cn/plugin?id=3796", + "update_log": ["- 优化 增加下拉箭头"] +} diff --git a/uni_modules/uni-data-picker/components/uni-data-picker/keypress.js b/uni_modules/uni-data-picker/components/uni-data-picker/keypress.js new file mode 100644 index 0000000..6ef26a2 --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-picker/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + this.$once('hook:beforeDestroy', () => { + document.removeEventListener('keyup', listener) + }) + }, + render: () => {} +} +// #endif diff --git a/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue b/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue new file mode 100644 index 0000000..fced5ec --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue @@ -0,0 +1,472 @@ + + + + + diff --git a/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js new file mode 100644 index 0000000..bb28500 --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js @@ -0,0 +1,545 @@ +export default { + props: { + localdata: { + type: [Array, Object], + default () { + return [] + } + }, + collection: { + type: String, + default: '' + }, + action: { + type: String, + default: '' + }, + field: { + type: String, + default: '' + }, + orderby: { + type: String, + default: '' + }, + where: { + type: [String, Object], + default: '' + }, + pageData: { + type: String, + default: 'add' + }, + pageCurrent: { + type: Number, + default: 1 + }, + pageSize: { + type: Number, + default: 20 + }, + getcount: { + type: [Boolean, String], + default: false + }, + getone: { + type: [Boolean, String], + default: false + }, + gettree: { + type: [Boolean, String], + default: false + }, + manual: { + type: Boolean, + default: false + }, + value: { + type: [Array, String, Number], + default () { + return [] + } + }, + modelValue: { + type: [Array, String, Number], + default () { + return [] + } + }, + preload: { + type: Boolean, + default: false + }, + stepSearh: { + type: Boolean, + default: true + }, + selfField: { + type: String, + default: '' + }, + parentField: { + type: String, + default: '' + }, + multiple: { + type: Boolean, + default: false + } + }, + data() { + return { + loading: false, + errorMessage: '', + loadMore: { + contentdown: '', + contentrefresh: '', + contentnomore: '' + }, + dataList: [], + selected: [], + selectedIndex: 0, + page: { + current: this.pageCurrent, + size: this.pageSize, + count: 0 + } + } + }, + computed: { + isLocaldata() { + return !this.collection.length + }, + postField() { + let fields = [this.field]; + if (this.parentField) { + fields.push(`${this.parentField} as parent_value`); + } + return fields.join(','); + }, + dataValue(){ + let isarr = Array.isArray(this.value) && this.value.length === 0 + let isstr = typeof this.value === 'string' && !this.value + let isnum = typeof this.value === 'number' && !this.value + + if(isarr || isstr || isnum){ + return this.modelValue + } + + return this.value + } + }, + created() { + this.$watch(() => { + var al = []; + ['pageCurrent', + 'pageSize', + 'value', + 'modelValue', + 'localdata', + 'collection', + 'action', + 'field', + 'orderby', + 'where', + 'getont', + 'getcount', + 'gettree' + ].forEach(key => { + al.push(this[key]) + }); + return al + }, (newValue, oldValue) => { + let needReset = false + for (let i = 2; i < newValue.length; i++) { + if (newValue[i] != oldValue[i]) { + needReset = true + break + } + } + if (newValue[0] != oldValue[0]) { + this.page.current = this.pageCurrent + } + this.page.size = this.pageSize + + this.onPropsChange() + }) + this._treeData = [] + }, + methods: { + onPropsChange() { + this._treeData = [] + }, + getCommand(options = {}) { + /* eslint-disable no-undef */ + let db = uniCloud.database() + + const action = options.action || this.action + if (action) { + db = db.action(action) + } + + const collection = options.collection || this.collection + db = db.collection(collection) + + const where = options.where || this.where + if (!(!where || !Object.keys(where).length)) { + db = db.where(where) + } + + const field = options.field || this.field + if (field) { + db = db.field(field) + } + + const orderby = options.orderby || this.orderby + if (orderby) { + db = db.orderBy(orderby) + } + + const current = options.pageCurrent !== undefined ? options.pageCurrent : this.page.current + const size = options.pageSize !== undefined ? options.pageSize : this.page.size + const getCount = options.getcount !== undefined ? options.getcount : this.getcount + const getTree = options.gettree !== undefined ? options.gettree : this.gettree + + const getOptions = { + getCount, + getTree + } + if (options.getTreePath) { + getOptions.getTreePath = options.getTreePath + } + + db = db.skip(size * (current - 1)).limit(size).get(getOptions) + + return db + }, + getNodeData(callback) { + if (this.loading) { + return + } + this.loading = true + this.getCommand({ + field: this.postField, + where: this._pathWhere() + }).then((res) => { + this.loading = false + this.selected = res.result.data + callback && callback() + }).catch((err) => { + this.loading = false + this.errorMessage = err + }) + }, + getTreePath(callback) { + if (this.loading) { + return + } + this.loading = true + + this.getCommand({ + field: this.postField, + getTreePath: { + startWith: `${this.selfField}=='${this.dataValue}'` + } + }).then((res) => { + this.loading = false + let treePath = [] + this._extractTreePath(res.result.data, treePath) + this.selected = treePath + callback && callback() + }).catch((err) => { + this.loading = false + this.errorMessage = err + }) + }, + loadData() { + if (this.isLocaldata) { + this._processLocalData() + return + } + + if (this.dataValue.length) { + this._loadNodeData((data) => { + this._treeData = data + this._updateBindData() + this._updateSelected() + }) + return + } + + if (this.stepSearh) { + this._loadNodeData((data) => { + this._treeData = data + this._updateBindData() + }) + } else { + this._loadAllData((data) => { + this._treeData = [] + this._extractTree(data, this._treeData, null) + this._updateBindData() + }) + } + }, + _loadAllData(callback) { + if (this.loading) { + return + } + this.loading = true + + this.getCommand({ + field: this.postField, + gettree: true, + startwith: `${this.selfField}=='${this.dataValue}'` + }).then((res) => { + this.loading = false + callback(res.result.data) + this.onDataChange() + }).catch((err) => { + this.loading = false + this.errorMessage = err + }) + }, + _loadNodeData(callback, pw) { + if (this.loading) { + return + } + this.loading = true + + this.getCommand({ + field: this.postField, + where: pw || this._postWhere(), + pageSize: 500 + }).then((res) => { + this.loading = false + callback(res.result.data) + this.onDataChange() + }).catch((err) => { + this.loading = false + this.errorMessage = err + }) + }, + _pathWhere() { + let result = [] + let where_field = this._getParentNameByField(); + if (where_field) { + result.push(`${where_field} == '${this.dataValue}'`) + } + + if (this.where) { + return `(${this.where}) && (${result.join(' || ')})` + } + + return result.join(' || ') + }, + _postWhere() { + let result = [] + let selected = this.selected + let parentField = this.parentField + if (parentField) { + result.push(`${parentField} == null || ${parentField} == ""`) + } + if (selected.length) { + for (var i = 0; i < selected.length - 1; i++) { + result.push(`${parentField} == '${selected[i].value}'`) + } + } + + let where = [] + if (this.where) { + where.push(`(${this.where})`) + } + if (result.length) { + where.push(`(${result.join(' || ')})`) + } + + return where.join(' && ') + }, + _nodeWhere() { + let result = [] + let selected = this.selected + if (selected.length) { + result.push(`${this.parentField} == '${selected[selected.length - 1].value}'`) + } + + if (this.where) { + return `(${this.where}) && (${result.join(' || ')})` + } + + return result.join(' || ') + }, + _getParentNameByField() { + const fields = this.field.split(','); + let where_field = null; + for (let i = 0; i < fields.length; i++) { + const items = fields[i].split('as'); + if (items.length < 2) { + continue; + } + if (items[1].trim() === 'value') { + where_field = items[0].trim(); + break; + } + } + return where_field + }, + _isTreeView() { + return (this.parentField && this.selfField) + }, + _updateSelected() { + var dl = this.dataList + var sl = this.selected + for (var i = 0; i < sl.length; i++) { + var value = sl[i].value + var dl2 = dl[i] + for (var j = 0; j < dl2.length; j++) { + var item2 = dl2[j] + if (item2.value === value) { + sl[i].text = item2.text + break + } + } + } + }, + _updateBindData(node) { + const { + dataList, + hasNodes + } = this._filterData(this._treeData, this.selected) + + let isleaf = this._stepSearh === false && !hasNodes + + if (node) { + node.isleaf = isleaf + } + + this.dataList = dataList + this.selectedIndex = dataList.length - 1 + + if (!isleaf && this.selected.length < dataList.length) { + this.selected.push({ + value: null, + text: "请选择" + }) + } + + return { + isleaf, + hasNodes + } + }, + _filterData(data, paths) { + let dataList = [] + + let hasNodes = true + + dataList.push(data.filter((item) => { + return item.parent_value === undefined + })) + for (let i = 0; i < paths.length; i++) { + var value = paths[i].value + var nodes = data.filter((item) => { + return item.parent_value === value + }) + + if (nodes.length) { + dataList.push(nodes) + } else { + hasNodes = false + } + } + + return { + dataList, + hasNodes + } + }, + _extractTree(nodes, result, parent_value) { + let list = result || [] + for (let i = 0; i < nodes.length; i++) { + let node = nodes[i] + + let child = {} + for (let key in node) { + if (key !== 'children') { + child[key] = node[key] + } + } + if (parent_value !== undefined) { + child.parent_value = parent_value + } + result.push(child) + + let children = node.children + if (children) { + this._extractTree(children, result, node.value) + } + } + }, + _extractTreePath(nodes, result) { + let list = result || [] + for (let i = 0; i < nodes.length; i++) { + let node = nodes[i] + + let child = {} + for (let key in node) { + if (key !== 'children') { + child[key] = node[key] + } + } + result.push(child) + + let children = node.children + if (children) { + this._extractTreePath(children, result) + } + } + }, + _findNodePath(key, nodes, path = []) { + for (let i = 0; i < nodes.length; i++) { + let { + value, + text, + children + } = nodes[i] + + path.push({ + value, + text + }) + + if (value === key) { + return path + } + + if (children) { + const p = this._findNodePath(key, children, path) + if (p.length) { + return p + } + } + + path.pop() + } + return [] + }, + _processLocalData() { + this._treeData = [] + this._extractTree(this.localdata, this._treeData) + + var inputValue = this.dataValue + if (inputValue === undefined) { + return + } + + if (Array.isArray(inputValue)) { + inputValue = inputValue[inputValue.length - 1] + if (typeof inputValue === 'object' && inputValue.value) { + inputValue = inputValue.value + } + } + + this.selected = this._findNodePath(inputValue, this.localdata) + } + } +} diff --git a/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue new file mode 100644 index 0000000..7b59529 --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue @@ -0,0 +1,300 @@ + + + + + diff --git a/uni_modules/uni-data-picker/package.json b/uni_modules/uni-data-picker/package.json new file mode 100644 index 0000000..469b065 --- /dev/null +++ b/uni_modules/uni-data-picker/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-data-picker", + "displayName": "uni-data-picker 数据驱动的picker选择器", + "version": "0.4.0", + "description": "Picker选择器", + "keywords": [ + "uni-ui", + "uniui", + "picker", + "级联", + "省市区", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-load-more" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-data-picker/readme.md b/uni_modules/uni-data-picker/readme.md new file mode 100644 index 0000000..70eed23 --- /dev/null +++ b/uni_modules/uni-data-picker/readme.md @@ -0,0 +1,270 @@ +## DataPicker 级联选择 +> **组件名:uni-data-picker** +> 代码块: `uDataPicker` +> 关联组件:`uni-data-pickerview`、`uni-load-more`。 + + +`` 是一个选择类[datacom组件](https://uniapp.dcloud.net.cn/component/datacom)。 + +支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部tab区域会左右滚动。 + +候选数据支持一次性加载完毕,也支持懒加载,比如示例图中,选择了“北京”后,动态加载北京的区县数据。 + +`` 组件尤其适用于地址选择、分类选择等选择类。 + +`` 支持本地数据、云端静态数据(json),uniCloud云数据库数据。 + +`` 可以通过JQL直连uniCloud云数据库,配套[DB Schema](https://uniapp.dcloud.net.cn/uniCloud/schema),可在schema2code中自动生成前端页面,还支持服务器端校验。 + +在uniCloud数据表中新建表“uni-id-address”和“opendb-city-china”,这2个表的schema自带foreignKey关联。在“uni-id-address”表的表结构页面使用schema2code生成前端页面,会自动生成地址管理的维护页面,自动从“opendb-city-china”表包含的中国所有省市区信息里选择地址。 + + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 +> - `` 内部包含了弹出层组件 `` 外层的布局可能会影响弹出层,[详情](https://developer.mozilla.org/zh-Hans/docs/Web/CSS/Common_CSS_Questions) + + + +### 平台差异说明 + +暂不支持在nvue页面中使用 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`componets`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +## API + +### DataPicker Props + +|属性名 | 类型 | 可选值 | 默认值 | 说明| +|:-: | :-: |:-: | :-: | :-: | +|v-model |String/ Number | - | - |绑定数据| +|localdata |Array | | |数据,[详情](https://gitee.com/dcloud/datacom)| +|preload |Boolean | true/false | false |预加载数据| +|readonly |Boolean | true/false | false |是否禁用| +|step-searh |Boolean | true/false | true |分步查询时,点击节点请求数据| +|step-search-url |String | | |分步查询时,动态加载云端数据url格式,`https://xxx.com/{parentValue}`(当前版本暂不支持,下版支持)| +|self-field |String | | |分步查询时当前字段名称| +|parent-field |String | | |分步查询时父字段名称| +|collection |String | | |表名。支持输入多个表名,用 `,` 分割| +|field |String | | |查询字段,多个字段用 `,` 分割| +|where |String | | |查询条件,内容较多,另见jql文档:[详情](https://uniapp.dcloud.net.cn/uniCloud/uni-clientDB?id=jsquery)| +|orderby |String | | |排序字段及正序倒叙设置| +|popup-title |String | | |弹出层标题| + + +> **** +> `collection/where/orderby` 和 `` 的用法一致,[详情](https://uniapp.dcloud.net.cn/uniCloud/unicloud-db) + + + +### DataPicker Events + +|事件称名 | 类型 | 说明 | +|:-: | :-: | :-: | +|@change |EventHandle | 选择完成时触发 {detail: {value}} | +|@nodeclick |EventHandle | 节点被点击时触发 | +|@stepsearch |EventHandle | 动态加载节点数据前触发(当前版本暂不支持,下版支持) | +|@popupopened |EventHandle | 弹出层弹出时触发 | +|@popupclosed |EventHandle | 弹出层关闭时触发 | + + + +### 基本用法 + +#### 云端数据 + +> **注意事项** +> - 云端数据需要关联服务空间 +> - 下面示例中使用的表 `opendb-city-china`(中国城市省市区数据,含港澳台), 在[uniCloud控制台](https://unicloud.dcloud.net.cn/)使用opendb创建,[详情](https://gitee.com/dcloud/opendb) + + +```html + +``` + +```js + + +``` + + + + + +#### 本地数据 + +```html + +``` + +```js + + +``` + + +#### 自定义solt + +```html + + + {{error}} + + + + {{item.text}} + + + + 请选择 + + +``` + + +> **注意事项** +> `localdata` 和 `collection` 同时配置时,`localdata` 优先 + + + +#### 完整示例 + +```html + +``` + +```js + + +``` + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/data-picker/data-picker](https://hellouniapp.dcloud.net.cn/pages/extUI/data-picker/data-picker) \ No newline at end of file diff --git a/uni_modules/uni-dateformat/changelog.md b/uni_modules/uni-dateformat/changelog.md new file mode 100644 index 0000000..7006d13 --- /dev/null +++ b/uni_modules/uni-dateformat/changelog.md @@ -0,0 +1,7 @@ +## 0.0.5(2021-07-08) +- 调整 默认时间不再是当前时间,而是显示'-'字符 +## 0.0.4(2021-05-12) +- 新增 组件示例地址 +## 0.0.3(2021-02-04) +- 调整为uni_modules目录规范 +- 修复 iOS 平台日期格式化出错的问题 diff --git a/uni_modules/uni-dateformat/components/uni-dateformat/date-format.js b/uni_modules/uni-dateformat/components/uni-dateformat/date-format.js new file mode 100644 index 0000000..e00d559 --- /dev/null +++ b/uni_modules/uni-dateformat/components/uni-dateformat/date-format.js @@ -0,0 +1,200 @@ +// yyyy-MM-dd hh:mm:ss.SSS 所有支持的类型 +function pad(str, length = 2) { + str += '' + while (str.length < length) { + str = '0' + str + } + return str.slice(-length) +} + +const parser = { + yyyy: (dateObj) => { + return pad(dateObj.year, 4) + }, + yy: (dateObj) => { + return pad(dateObj.year) + }, + MM: (dateObj) => { + return pad(dateObj.month) + }, + M: (dateObj) => { + return dateObj.month + }, + dd: (dateObj) => { + return pad(dateObj.day) + }, + d: (dateObj) => { + return dateObj.day + }, + hh: (dateObj) => { + return pad(dateObj.hour) + }, + h: (dateObj) => { + return dateObj.hour + }, + mm: (dateObj) => { + return pad(dateObj.minute) + }, + m: (dateObj) => { + return dateObj.minute + }, + ss: (dateObj) => { + return pad(dateObj.second) + }, + s: (dateObj) => { + return dateObj.second + }, + SSS: (dateObj) => { + return pad(dateObj.millisecond, 3) + }, + S: (dateObj) => { + return dateObj.millisecond + }, +} + +// 这都n年了iOS依然不认识2020-12-12,需要转换为2020/12/12 +function getDate(time) { + if (time instanceof Date) { + return time + } + switch (typeof time) { + case 'string': + { + // 2020-12-12T12:12:12.000Z、2020-12-12T12:12:12.000 + if (time.indexOf('T') > -1) { + return new Date(time) + } + return new Date(time.replace(/-/g, '/')) + } + default: + return new Date(time) + } +} + +export function formatDate(date, format = 'yyyy/MM/dd hh:mm:ss') { + if (!date && date !== 0) { + return '' + } + date = getDate(date) + const dateObj = { + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate(), + hour: date.getHours(), + minute: date.getMinutes(), + second: date.getSeconds(), + millisecond: date.getMilliseconds() + } + const tokenRegExp = /yyyy|yy|MM|M|dd|d|hh|h|mm|m|ss|s|SSS|SS|S/ + let flag = true + let result = format + while (flag) { + flag = false + result = result.replace(tokenRegExp, function(matched) { + flag = true + return parser[matched](dateObj) + }) + } + return result +} + +export function friendlyDate(time, { + locale = 'zh', + threshold = [60000, 3600000], + format = 'yyyy/MM/dd hh:mm:ss' +}) { + if (time === '-') { + return time + } + if (!time && time !== 0) { + return '' + } + const localeText = { + zh: { + year: '年', + month: '月', + day: '天', + hour: '小时', + minute: '分钟', + second: '秒', + ago: '前', + later: '后', + justNow: '刚刚', + soon: '马上', + template: '{num}{unit}{suffix}' + }, + en: { + year: 'year', + month: 'month', + day: 'day', + hour: 'hour', + minute: 'minute', + second: 'second', + ago: 'ago', + later: 'later', + justNow: 'just now', + soon: 'soon', + template: '{num} {unit} {suffix}' + } + } + const text = localeText[locale] || localeText.zh + let date = getDate(time) + let ms = date.getTime() - Date.now() + let absMs = Math.abs(ms) + if (absMs < threshold[0]) { + return ms < 0 ? text.justNow : text.soon + } + if (absMs >= threshold[1]) { + return formatDate(date, format) + } + let num + let unit + let suffix = text.later + if (ms < 0) { + suffix = text.ago + ms = -ms + } + const seconds = Math.floor((ms) / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + const days = Math.floor(hours / 24) + const months = Math.floor(days / 30) + const years = Math.floor(months / 12) + switch (true) { + case years > 0: + num = years + unit = text.year + break + case months > 0: + num = months + unit = text.month + break + case days > 0: + num = days + unit = text.day + break + case hours > 0: + num = hours + unit = text.hour + break + case minutes > 0: + num = minutes + unit = text.minute + break + default: + num = seconds + unit = text.second + break + } + + if (locale === 'en') { + if (num === 1) { + num = 'a' + } else { + unit += 's' + } + } + + return text.template.replace(/{\s*num\s*}/g, num + '').replace(/{\s*unit\s*}/g, unit).replace(/{\s*suffix\s*}/g, + suffix) +} diff --git a/uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue b/uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue new file mode 100644 index 0000000..c5ed030 --- /dev/null +++ b/uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/uni_modules/uni-dateformat/package.json b/uni_modules/uni-dateformat/package.json new file mode 100644 index 0000000..64ec8cf --- /dev/null +++ b/uni_modules/uni-dateformat/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-dateformat", + "displayName": "uni-dateformat 日期格式化", + "version": "0.0.5", + "description": "日期格式化组件,可以将日期格式化为1分钟前、刚刚等形式", + "keywords": [ + "uni-ui", + "uniui", + "日期格式化", + "时间格式化", + "格式化时间", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "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" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-dateformat/readme.md b/uni_modules/uni-dateformat/readme.md new file mode 100644 index 0000000..7d2da4a --- /dev/null +++ b/uni_modules/uni-dateformat/readme.md @@ -0,0 +1,77 @@ + + +### DateFormat 日期格式化 +> **组件名:uni-dateformat** +> 代码块: `uDateformat` + + +日期格式化组件。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + + + + +``` + +## API + +### Dateformat Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|date |Object|String|Number |Date.now() |要格式化的日期对象/日期字符串/时间戳 | +|threshold |Array |[0, 0] |转化类型阈值 | +|format |String |'yyyy/MM/dd hh:mm:ss' |格式字符串 | +|locale |String |zh |格式化使用的语言,目前支持zh(中文)、en(英文) | + + +#### Threshold Options + +格式化组件会对时间进行用户友好转化,threshold就是用来控制转化的时间阈值的。 + +以`[60000, 3600000]`为例,将传入时间与当前时间差的绝对值记为delta(单位毫秒) + +- `delta < 60000`时,时间会被转化为“刚刚|马上” +- `delta >= 60000 && delta < 3600000`时,时间会被转化为“xx分钟前|xx分钟后”,如果超过1小时会显示成“xx小时前|xx小时后”,以此类推 +- `delta >= 3600000`时,会按照format参数传入的格式进行格式化 + +如果不想转化为“马上|刚刚”可以传入`:threshold = "[0,3600000]"`。默认值`[0,0]`既不会转换为“马上|刚刚”也不会转化为“xx分钟前|xx分钟后” + +#### Format Options + +format接收字符以及含义如下: + +|字符 |说明 | +|:-: |:-: | +|yyyy |四位年份 | +|yy |两位年份 | +|MM |两位月份(不足两位在前面补0) | +|M |月份,不自动补0 | +|dd |两位天(不足两位在前面补0) | +|d |天,不自动补0 | +|hh |两位小时(不足两位在前面补0) | +|h |小时,不自动补0 | +|mm |两位分钟(不足两位在前面补0) | +|m |分钟,不自动补0 | +|ss |两位秒(不足两位在前面补0) | +|s |秒,不自动补0 | +|SSS |三位毫秒(不足三位在前面补0) | +|S |毫秒,不自动补0 | + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/dateformat/dateformat](https://hellouniapp.dcloud.net.cn/pages/extUI/dateformat/dateformat) \ No newline at end of file diff --git a/uni_modules/uni-datetime-picker/changelog.md b/uni_modules/uni-datetime-picker/changelog.md new file mode 100644 index 0000000..1011ada --- /dev/null +++ b/uni_modules/uni-datetime-picker/changelog.md @@ -0,0 +1,65 @@ +## 2.1.1(2021-08-24) +- 新增 支持国际化 +- 优化 范围选择器在 pc 端过宽的问题 +## 2.1.0(2021-08-09) +- 新增 适配 vue3 +## 2.0.19(2021-08-09) +- 新增 支持作为 uni-forms 子组件相关功能 +- 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的 bug +## 2.0.18(2021-08-05) +- 修复 type 属性动态赋值无效的 bug +- 修复 ‘确认’按钮被 tabbar 遮盖 bug +- 修复 组件未赋值时范围选左、右日历相同的 bug +## 2.0.17(2021-08-04) +- 修复 范围选未正确显示当前值的 bug +- 修复 h5 平台(移动端)报错 'cale' of undefined 的 bug +## 2.0.16(2021-07-21) +- 新增 return-type 属性支持返回 date 日期对象 +## 2.0.15(2021-07-14) +- 修复 单选日期类型,初始赋值后不在当前日历的 bug +- 新增 clearIcon 属性,显示框的清空按钮可配置显示隐藏(仅 pc 有效) +- 优化 移动端移除显示框的清空按钮,无实际用途 +## 2.0.14(2021-07-14) +- 修复 组件赋值为空,界面未更新的 bug +- 修复 start 和 end 不能动态赋值的 bug +- 修复 范围选类型,用户选择后再次选择右侧日历(结束日期)显示不正确的 bug +## 2.0.13(2021-07-08) +- 修复 范围选择不能动态赋值的 bug +## 2.0.12(2021-07-08) +- 修复 范围选择的初始时间在一个月内时,造成无法选择的bug +## 2.0.11(2021-07-08) +- 优化 弹出层在超出视窗边缘定位不准确的问题 +## 2.0.10(2021-07-08) +- 修复 范围起始点样式的背景色与今日样式的字体前景色融合,导致日期字体看不清的 bug +- 优化 弹出层在超出视窗边缘被遮盖的问题 +## 2.0.9(2021-07-07) +- 新增 maskClick 事件 +- 修复 特殊情况日历 rpx 布局错误的 bug,rpx -> px +- 修复 范围选择时清空返回值不合理的bug,['', ''] -> [] +## 2.0.8(2021-07-07) +- 新增 日期时间显示框支持插槽 +## 2.0.7(2021-07-01) +- 优化 添加 uni-icons 依赖 +## 2.0.6(2021-05-22) +- 修复 图标在小程序上不显示的 bug +- 优化 重命名引用组件,避免潜在组件命名冲突 +## 2.0.5(2021-05-20) +- 优化 代码目录扁平化 +## 2.0.4(2021-05-12) +- 新增 组件示例地址 +## 2.0.3(2021-05-10) +- 修复 ios 下不识别 '-' 日期格式的 bug +- 优化 pc 下弹出层添加边框和阴影 +## 2.0.2(2021-05-08) +- 修复 在 admin 中获取弹出层定位错误的bug +## 2.0.1(2021-05-08) +- 修复 type 属性向下兼容,默认值从 date 变更为 datetime +## 2.0.0(2021-04-30) +- 支持日历形式的日期+时间的范围选择 + > 注意:此版本不向后兼容,不再支持单独时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker) +## 1.0.6(2021-03-18) +- 新增 hide-second 属性,时间支持仅选择时、分 +- 修复 选择跟显示的日期不一样的 bug +- 修复 chang事件触发2次的 bug +- 修复 分、秒 end 范围错误的 bug +- 优化 更好的 nvue 适配 diff --git a/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue new file mode 100644 index 0000000..608dd78 --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue @@ -0,0 +1,183 @@ + + + + + diff --git a/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.js b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.js new file mode 100644 index 0000000..b8d7d6f --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/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/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue new file mode 100644 index 0000000..73e9493 --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue @@ -0,0 +1,801 @@ + + + + + diff --git a/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json new file mode 100644 index 0000000..cc76311 --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json @@ -0,0 +1,19 @@ +{ + "uni-datetime-picker.selectDate": "select date", + "uni-datetime-picker.selectTime": "select time", + "uni-datetime-picker.selectDateTime": "select datetime", + "uni-datetime-picker.startDate": "start date", + "uni-datetime-picker.endDate": "end date", + "uni-datetime-picker.startTime": "start time", + "uni-datetime-picker.endTime": "end time", + "uni-datetime-picker.ok": "ok", + "uni-datetime-picker.clear": "clear", + "uni-datetime-picker.cancel": "cancel", + "uni-calender.MON": "MON", + "uni-calender.TUE": "TUE", + "uni-calender.WED": "WED", + "uni-calender.THU": "THU", + "uni-calender.FRI": "FRI", + "uni-calender.SAT": "SAT", + "uni-calender.SUN": "SUN" +} diff --git a/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json new file mode 100644 index 0000000..7bc7405 --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json @@ -0,0 +1,19 @@ +{ + "uni-datetime-picker.selectDate": "选择日期", + "uni-datetime-picker.selectTime": "选择时间", + "uni-datetime-picker.selectDateTime": "选择日期时间", + "uni-datetime-picker.startDate": "开始日期", + "uni-datetime-picker.endDate": "结束日期", + "uni-datetime-picker.startTime": "开始时间", + "uni-datetime-picker.endTime": "结束时间", + "uni-datetime-picker.ok": "确定", + "uni-datetime-picker.clear": "清除", + "uni-datetime-picker.cancel": "取消", + "uni-calender.SUN": "日", + "uni-calender.MON": "一", + "uni-calender.TUE": "二", + "uni-calender.WED": "三", + "uni-calender.THU": "四", + "uni-calender.FRI": "五", + "uni-calender.SAT": "六" +} diff --git a/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json new file mode 100644 index 0000000..7d37043 --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json @@ -0,0 +1,19 @@ +{ + "uni-datetime-picker.selectDate": "選擇日期", + "uni-datetime-picker.selectTime": "選擇時間", + "uni-datetime-picker.selectDateTime": "選擇日期時間", + "uni-datetime-picker.startDate": "開始日期", + "uni-datetime-picker.endDate": "結束日期", + "uni-datetime-picker.startTime": "開始时间", + "uni-datetime-picker.endTime": "結束时间", + "uni-datetime-picker.ok": "確定", + "uni-datetime-picker.clear": "清除", + "uni-datetime-picker.cancel": "取消", + "uni-calender.SUN": "日", + "uni-calender.MON": "一", + "uni-calender.TUE": "二", + "uni-calender.WED": "三", + "uni-calender.THU": "四", + "uni-calender.FRI": "五", + "uni-calender.SAT": "六" +} diff --git a/uni_modules/uni-datetime-picker/components/uni-datetime-picker/keypress.js b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/keypress.js new file mode 100644 index 0000000..9601aba --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + this.$once('hook:beforeDestroy', () => { + document.removeEventListener('keyup', listener) + }) + }, + render: () => {} +} +// #endif \ No newline at end of file diff --git a/uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue new file mode 100644 index 0000000..1748de5 --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue @@ -0,0 +1,924 @@ + + + + + diff --git a/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue new file mode 100644 index 0000000..7d75ebf --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue @@ -0,0 +1,951 @@ + + + + diff --git a/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js new file mode 100644 index 0000000..1f9f977 --- /dev/null +++ b/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js @@ -0,0 +1,408 @@ +import CALENDAR from './calendar.js' + +class Calendar { + constructor({ + date, + selected, + startDate, + endDate, + range, + // multipleStatus + } = {}) { + // 当前日期 + this.date = this.getDate(new Date()) // 当前初入日期 + // 打点信息 + this.selected = selected || []; + // 范围开始 + this.startDate = startDate + // 范围结束 + this.endDate = endDate + this.range = range + // 多选状态 + this.cleanMultipleStatus() + // 每周日期 + this.weeks = {} + // this._getWeek(this.date.fullDate) + // this.multipleStatus = multipleStatus + this.lastHover = false + } + /** + * 设置日期 + * @param {Object} date + */ + setDate(date) { + this.selectDate = this.getDate(date) + this._getWeek(this.selectDate.fullDate) + } + + /** + * 清理多选状态 + */ + cleanMultipleStatus() { + this.multipleStatus = { + before: '', + after: '', + data: [] + } + } + + /** + * 重置开始日期 + */ + resetSatrtDate(startDate) { + // 范围开始 + this.startDate = startDate + + } + + /** + * 重置结束日期 + */ + resetEndDate(endDate) { + // 范围结束 + this.endDate = endDate + } + + /** + * 获取任意时间 + */ + getDate(date, AddDayCount = 0, str = 'day') { + if (!date) { + date = new Date() + } + if (typeof date !== 'object') { + date = date.replace(/-/g, '/') + } + const dd = new Date(date) + switch (str) { + case 'day': + dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期 + break + case 'month': + if (dd.getDate() === 31) { + dd.setDate(dd.getDate() + AddDayCount) + } else { + dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期 + } + break + case 'year': + dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期 + break + } + const y = dd.getFullYear() + const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0 + const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0 + return { + fullDate: y + '-' + m + '-' + d, + year: y, + month: m, + date: d, + day: dd.getDay() + } + } + + + /** + * 获取上月剩余天数 + */ + _getLastMonthDays(firstDay, full) { + let dateArr = [] + for (let i = firstDay; i > 0; i--) { + const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate() + dateArr.push({ + date: beforeDate, + month: full.month - 1, + lunar: this.getlunar(full.year, full.month - 1, beforeDate), + disable: true + }) + } + return dateArr + } + /** + * 获取本月天数 + */ + _currentMonthDys(dateData, full) { + let dateArr = [] + let fullDate = this.date.fullDate + for (let i = 1; i <= dateData; i++) { + let isinfo = false + let nowDate = full.year + '-' + (full.month < 10 ? + full.month : full.month) + '-' + (i < 10 ? + '0' + i : i) + // 是否今天 + let isDay = fullDate === nowDate + // 获取打点信息 + let info = this.selected && this.selected.find((item) => { + if (this.dateEqual(nowDate, item.date)) { + return item + } + }) + + // 日期禁用 + let disableBefore = true + let disableAfter = true + if (this.startDate) { + // let dateCompBefore = this.dateCompare(this.startDate, fullDate) + // disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate) + disableBefore = this.dateCompare(this.startDate, nowDate) + } + + if (this.endDate) { + // let dateCompAfter = this.dateCompare(fullDate, this.endDate) + // disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate) + disableAfter = this.dateCompare(nowDate, this.endDate) + } + let multiples = this.multipleStatus.data + let checked = false + let multiplesStatus = -1 + if (this.range) { + if (multiples) { + multiplesStatus = multiples.findIndex((item) => { + return this.dateEqual(item, nowDate) + }) + } + if (multiplesStatus !== -1) { + checked = true + } + } + let data = { + fullDate: nowDate, + year: full.year, + date: i, + multiple: this.range ? checked : false, + beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate), + afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate), + month: full.month, + lunar: this.getlunar(full.year, full.month, i), + disable: !(disableBefore && disableAfter), + isDay + } + if (info) { + data.extraInfo = info + } + + dateArr.push(data) + } + return dateArr + } + /** + * 获取下月天数 + */ + _getNextMonthDays(surplus, full) { + let dateArr = [] + for (let i = 1; i < surplus + 1; i++) { + dateArr.push({ + date: i, + month: Number(full.month) + 1, + lunar: this.getlunar(full.year, Number(full.month) + 1, i), + disable: true + }) + } + return dateArr + } + + /** + * 获取当前日期详情 + * @param {Object} date + */ + getInfo(date) { + if (!date) { + date = new Date() + } + const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate) + return dateInfo + } + + /** + * 比较时间大小 + */ + dateCompare(startDate, endDate) { + // 计算截止时间 + startDate = new Date(startDate.replace('-', '/').replace('-', '/')) + // 计算详细项的截止时间 + endDate = new Date(endDate.replace('-', '/').replace('-', '/')) + if (startDate <= endDate) { + return true + } else { + return false + } + } + + /** + * 比较时间是否相等 + */ + dateEqual(before, after) { + // 计算截止时间 + before = new Date(before.replace('-', '/').replace('-', '/')) + // 计算详细项的截止时间 + after = new Date(after.replace('-', '/').replace('-', '/')) + if (before.getTime() - after.getTime() === 0) { + return true + } else { + return false + } + } + + + /** + * 获取日期范围内所有日期 + * @param {Object} begin + * @param {Object} end + */ + geDateAll(begin, end) { + var arr = [] + var ab = begin.split('-') + var ae = end.split('-') + var db = new Date() + db.setFullYear(ab[0], ab[1] - 1, ab[2]) + var de = new Date() + de.setFullYear(ae[0], ae[1] - 1, ae[2]) + var unixDb = db.getTime() - 24 * 60 * 60 * 1000 + var unixDe = de.getTime() - 24 * 60 * 60 * 1000 + for (var k = unixDb; k <= unixDe;) { + k = k + 24 * 60 * 60 * 1000 + arr.push(this.getDate(new Date(parseInt(k))).fullDate) + } + return arr + } + /** + * 计算阴历日期显示 + */ + getlunar(year, month, date) { + return CALENDAR.solar2lunar(year, month, date) + } + /** + * 设置打点 + */ + setSelectInfo(data, value) { + this.selected = value + this._getWeek(data) + } + + /** + * 获取多选状态 + */ + setMultiple(fullDate) { + let { + before, + after + } = this.multipleStatus + + if (!this.range) return + if (before && after) { + if (!this.lastHover) { + this.lastHover = true + return + } + this.multipleStatus.before = '' + this.multipleStatus.after = '' + this.multipleStatus.data = [] + this.multipleStatus.fulldate = '' + this.lastHover = false + } else { + this.lastHover = false + if (!before) { + this.multipleStatus.before = fullDate + } else { + this.multipleStatus.after = fullDate + if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus + .after); + } else { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus + .before); + } + } + } + this._getWeek(fullDate) + } + + /** + * 鼠标 hover 更新多选状态 + */ + setHoverMultiple(fullDate) { + let { + before, + after + } = this.multipleStatus + + if (!this.range) return + if (this.lastHover) return + + if (!before) { + this.multipleStatus.before = fullDate + } else { + this.multipleStatus.after = fullDate + if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after); + } else { + this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before); + } + } + this._getWeek(fullDate) + } + + /** + * 更新默认值多选状态 + */ + setDefaultMultiple(before, after) { + this.multipleStatus.before = before + this.multipleStatus.after = after + if (before && after) { + if (this.dateCompare(before, after)) { + this.multipleStatus.data = this.geDateAll(before, after); + this._getWeek(after) + } else { + this.multipleStatus.data = this.geDateAll(after, before); + this._getWeek(before) + } + } + } + + /** + * 获取每周数据 + * @param {Object} dateData + */ + _getWeek(dateData) { + const { + fullDate, + year, + month, + date, + day + } = this.getDate(dateData) + let firstDay = new Date(year, month - 1, 1).getDay() + let currentDay = new Date(year, month, 0).getDate() + let dates = { + lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天 + currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数 + nextMonthDays: [], // 下个月开始几天 + weeks: [] + } + let canlender = [] + const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length) + dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData)) + canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays) + let weeks = {} + // 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天 + for (let i = 0; i < canlender.length; i++) { + if (i % 7 === 0) { + weeks[parseInt(i / 7)] = new Array(7) + } + weeks[parseInt(i / 7)][i % 7] = canlender[i] + } + this.canlender = canlender + this.weeks = weeks + } + + //静态方法 + // static init(date) { + // if (!this.instance) { + // this.instance = new Calendar(date); + // } + // return this.instance; + // } +} + + +export default Calendar diff --git a/uni_modules/uni-datetime-picker/package.json b/uni_modules/uni-datetime-picker/package.json new file mode 100644 index 0000000..ad0b2b0 --- /dev/null +++ b/uni_modules/uni-datetime-picker/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-datetime-picker", + "displayName": "uni-datetime-picker 日期选择器", + "version": "2.1.1", + "description": "uni-datetime-picker 日期时间选择器,支持日历,支持范围选择", + "keywords": [ + "uni-datetime-picker", + "uni-ui", + "uniui", + "日期时间选择器", + "日期时间" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} diff --git a/uni_modules/uni-datetime-picker/readme.md b/uni_modules/uni-datetime-picker/readme.md new file mode 100644 index 0000000..1902ae1 --- /dev/null +++ b/uni_modules/uni-datetime-picker/readme.md @@ -0,0 +1,155 @@ + +> `重要通知:组件升级更新 2.0.0 后,支持日期+时间范围选择,组件 ui 将使用日历选择日期,ui 变化较大,同时支持 PC 和 移动端。此版本不向后兼容,不再支持单独的时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker)。若仍需使用旧版本,可在插件市场下载*非uni_modules版本*,旧版本将不再维护` +## DatetimePicker 时间选择器 +> **组件名:uni-datetime-picker** +> 代码块: `uDatetimePicker` + + +该组件的优势是,支持**时间戳**输入和输出(起始时间、终止时间也支持时间戳),可**同时选择**日期和时间。 + +若只是需要单独选择日期和时间,不需要时间戳输入和输出,可使用原生的 picker 组件。 + + +___点击 picker 默认值规则:___ + +- 若设置初始值 value, 会显示在 picker 显示框中 +- 若无初始值 value,则初始值 value 为当前本地时间 Date.now(), 但不会显示在 picker 显示框中 + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + + + + +``` + +## API + +### DatetimePicker Props + +|属性名 |类型 |默认值 |值域 |说明 | +|:-: |:-: |:-: | |:-: | +|type |String |datetime |date/daterange/datetime/datetimerange|选择器类型 | +|value |String、Number、Array(范围选择)、Date|- |- |输入框当前值 | +|start |String、Number |- |- |最小值,可以使用日期的字符串(String)、时间戳(Number) | +|end |String、Number |- |- |最大值,可以使用日期的字符串(String)、时间戳(Number) | +|return-type |String |string |timestamp 、string、date |返回值格式 | +|border |Boolean、String |true | |是否有边框 | +|rangeSeparator |String |'-' |- |选择范围时的分隔符 | +|placeholder |String |- |- |非范围选择时的占位内容 | +|start-placeholder|String |- |- |范围选择时开始日期的占位内容 | +|end-placeholder |String |- |- |范围选择时结束日期的占位内容 | +|disabled |Boolean、String |false | |是否不可选择 | +|clearIcon |Boolean、String |true | |是否显示清除按钮(仅PC端适用) | + + + + +### DatetimePicker Events + +|事件名称 |说明 |返回值 | +|:-: |:-: |:-: | +|change |确定日期时间时触发的事件,参数为当前选择的日期对象 |单选返回日期字符串,如:'2010-02-3';范围选返回日期字符串数组,如:['2020-10-1', '2021-4-1'] | +|maskClick|点击遮罩层触发 |- | + +### Popup Methods + +|方法称名 |说明|参数| +|:-:|:-:|:-:| +|show|打开弹出层|-| +|close|关闭弹出层 |-| + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/datetime-picker/datetime-picker](https://hellouniapp.dcloud.net.cn/pages/extUI/datetime-picker/datetime-picker) \ No newline at end of file diff --git a/uni_modules/uni-drawer/changelog.md b/uni_modules/uni-drawer/changelog.md new file mode 100644 index 0000000..c8eed01 --- /dev/null +++ b/uni_modules/uni-drawer/changelog.md @@ -0,0 +1,8 @@ +## 1.1.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-02-04) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-drawer/components/uni-drawer/keypress.js b/uni_modules/uni-drawer/components/uni-drawer/keypress.js new file mode 100644 index 0000000..62dda46 --- /dev/null +++ b/uni_modules/uni-drawer/components/uni-drawer/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + // this.$once('hook:beforeDestroy', () => { + // document.removeEventListener('keyup', listener) + // }) + }, + render: () => {} +} +// #endif diff --git a/uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue b/uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue new file mode 100644 index 0000000..c3cb00b --- /dev/null +++ b/uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue @@ -0,0 +1,182 @@ + + + + + diff --git a/uni_modules/uni-drawer/package.json b/uni_modules/uni-drawer/package.json new file mode 100644 index 0000000..0fad43f --- /dev/null +++ b/uni_modules/uni-drawer/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-drawer", + "displayName": "uni-drawer 抽屉", + "version": "1.1.1", + "description": "抽屉式导航,用于展示侧滑菜单,侧滑导航。", + "keywords": [ + "uni-ui", + "uniui", + "drawer", + "抽屉", + "侧滑导航" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-drawer/readme.md b/uni_modules/uni-drawer/readme.md new file mode 100644 index 0000000..d4a8b15 --- /dev/null +++ b/uni_modules/uni-drawer/readme.md @@ -0,0 +1,86 @@ + + +## Drawer 抽屉 +> **组件名:uni-drawer** +> 代码块: `uDrawer` + + +抽屉侧滑菜单。 + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - `width` 属性仅在 `vue` 页面生效,`nvue` 页面因性能问题,不支持动态设置宽度,如需修改,请下载组件修改源码 + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + + +在 ``template`` 中使用组件 + +```html + + + { + timer = null; + }, wait); + if (callNow) func.apply(context, args); + } else { + timer = setTimeout(() => { + func.apply(context, args); + }, wait) + } + } +} +/** + * @desc 函数节流 + * @param func 函数 + * @param wait 延迟执行毫秒数 + * @param type 1 使用表时间戳,在时间段开始的时候触发 2 使用表定时器,在时间段结束的时候触发 + */ +export const throttle = (func, wait = 1000, type = 1) => { + let previous = 0; + let timeout; + return function() { + let context = this; + let args = arguments; + if (type === 1) { + let now = Date.now(); + + if (now - previous > wait) { + func.apply(context, args); + previous = now; + } + } else if (type === 2) { + if (!timeout) { + timeout = setTimeout(() => { + timeout = null; + func.apply(context, args) + }, wait) + } + } + } +} diff --git a/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue b/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue new file mode 100644 index 0000000..16911f2 --- /dev/null +++ b/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue @@ -0,0 +1,461 @@ + + + + + diff --git a/uni_modules/uni-easyinput/package.json b/uni_modules/uni-easyinput/package.json new file mode 100644 index 0000000..cb436ac --- /dev/null +++ b/uni_modules/uni-easyinput/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-easyinput", + "displayName": "uni-easyinput 增强输入框", + "version": "0.1.4", + "description": "Easyinput 组件是对原生input组件的增强", + "keywords": [ + "uni-ui", + "uniui", + "input", + "uni-easyinput", + "输入框" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-easyinput/readme.md b/uni_modules/uni-easyinput/readme.md new file mode 100644 index 0000000..ab6c50b --- /dev/null +++ b/uni_modules/uni-easyinput/readme.md @@ -0,0 +1,198 @@ + + +### Easyinput 增强输入框 +> **组件名:uni-easyinput** +> 代码块: `uEasyinput` + + +easyinput 组件是对原生input组件的增强 ,是专门为配合表单组件[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)而设计的,easyinput 内置了边框,图标等,同时包含 input 所有功能 + + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + +### 平台差异说明 + +暂不支持在nvue页面中使用 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +输入内容后,输入框尾部会显示清除按钮,点击可清除内容,如不需要展示图标,`clearable` 属性设置为 `false` 即可 + +`clearable` 属性设置为 `true` ,输入框聚焦且内容不为空时,才会显示内容 + +```html + +``` + + +### 输入框带左右图标 + +设置 `prefixIcon` 属性来显示输入框的头部图标 + +设置 `suffixIcon` 属性来显示输入框的尾部图标 + +注意图标当前只支持 `uni-icons` 内置的图标,当配置 `suffixIcon` 属性后,会覆盖 `:clearable="true"` 和 `type="password"` 时的原有图标 + +绑定 `@iconClick` 事件可以触发图标的点击 ,返回 `prefix` 表示点击左侧图标,返回 `suffix` 表示点击右侧图标 + +```html + + + + + +``` + +### 输入框禁用 + +设置 `disable` 属性可以禁用输入框,此时输入框不可编辑 + +```html + +``` + +### 密码框 + +设置 `type="password"` 时,输入框内容将会不可见,由实心点代替,同时输入框尾部会显示眼睛图标,点击可切换内容显示状态 + +```html + +``` + +### 输入框聚焦 + +设置 `focus` 属性可以使输入框聚焦 + +如果页面存在多个设置 `focus` 属性的输入框,只有最后一个输入框的 `focus` 属性会生效 + +```html + +``` + + +### 多行文本 + +设置 `type="textarea"` 时可输入多行文本 + +```html + +``` + +### 多行文本自动高度 + +设置 `type="textarea"` 时且设置 `autoHeight` 属性,可使用多行文本的自动高度,会跟随内容调整输入框的显示高度 + +```html + +``` + +### 取消边框 + +设置 `:inputBorder="false"` 时可取消输入框的边框显示,同时搭配 `uni-forms` 的 `:border="true"` 有较好的效果 + +```html + + + + + + + + +``` + + +## API + +### Easyinput Props + +|属性名| 类型| 可选值|默认值|说明| +|:-:|:-:|:-:|:-:|:-:| +|value|String/ Number|-|-|输入内容| +|type|String|见 type Options|text|输入框的类型(默认text)| +|clearable|Boolean|-|true| 是否显示右侧清空内容的图标控件(输入框有内容且不禁用时显示),点击可清空输入框内容| +|autoHeight|Boolean|-|false| 是否自动增高输入区域,type为textarea时有效| +|placeholder|String |-| - | 输入框的提示文字| +|placeholderStyle|String| - | - | placeholder的样式(内联样式,字符串),如"color: #ddd"| +|focus|Boolean|-|false|是否自动获得焦点| +|disabled|Boolean|-|false|是否不可输入| +|maxlength|Number|-|140|最大输入长度,设置为 -1 的时候不限制最大长度| +|confirmType|String|-|done|设置键盘右下角按钮的文字,仅在type="text"时生效| +|clearSize|Number|-|15|清除图标的大小,单位px| +|prefixIcon|String|-|-|输入框头部图标 | +|suffixIcon|String|-|-|输入框尾部图标| +|trim|Boolean/String|见 trim Options | false | 是否自动去除空格,传入类型为 Boolean 时,自动去除前后空格| +|inputBorder|Boolean|-|true|是否显示input输入框的边框| +|styles|Object|-|-| 样式自定义| +|passwordIcon|Boolean|-| true | type=password 时,是否显示小眼睛图标| + + +#### Type Options + +|属性名| 说明| +|:-:| :-:| +|text|文本输入键盘| +|textarea |多行文本输入键盘| +|password |密码输入键盘| +|number|数字输入键盘,注意iOS上app-vue弹出的数字键盘并非9宫格方式 | +|idcard|身份证输入键盘,仅支持微信、支付宝、百度、QQ小程序| +|digit|带小数点的数字键盘,仅支持微信、支付宝、百度、头条、QQ小程序 | + +#### ConfirmType Options + +平台差异与 [input](https://uniapp.dcloud.io/component/input) 相同 + +|属性名 | 说明| +|:-:| :-:| +|send|右下角按钮为“发送” | +|search |右下角按钮为“搜索” | +|next|右下角按钮为“下一个”| +|go|右下角按钮为“前往” | +|done|右下角按钮为“完成” | + + +#### Styles Options + +|属性名| 默认值 |说明| +|:-:| :-:| :-:| +|color| #333| 输入文字颜色| +|disableColor |#eee| 输入框禁用背景色| +|borderColor |#e5e5e5 | 边框颜色| + +#### Trim Options + +传入类型为 `Boolean` 时,自动去除前后空格,传入类型为 `String` 时,可以单独控制,下面是可选值 + +|属性名|说明| +|:-:| :-:| +|both|去除两端空格| +|left|去除左侧空格| +|right|去除右侧空格| +|all|去除所有空格| +|none|不去除空格| + + +### Easyinput Events + +|事件称名| 说明|返回值| +|:-:| :-:|:-:| +|@input|输入框内容发生变化时触发| -| +|@focus|输入框获得焦点时触发| -| +|@blur|输入框失去焦点时触发| -| +|@confirm|点击完成按钮时触发| -| +|@iconClick |点击图标时触发| prefix/suffix | + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/easyinput/easyinput](https://hellouniapp.dcloud.net.cn/pages/extUI/easyinput/easyinput) \ No newline at end of file diff --git a/uni_modules/uni-fab/changelog.md b/uni_modules/uni-fab/changelog.md new file mode 100644 index 0000000..cbf69d6 --- /dev/null +++ b/uni_modules/uni-fab/changelog.md @@ -0,0 +1,8 @@ +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-02-05) +- 调整为uni_modules目录规范 +- 优化 按钮背景色调整 +- 优化 兼容pc端 diff --git a/uni_modules/uni-fab/components/uni-fab/uni-fab.vue b/uni_modules/uni-fab/components/uni-fab/uni-fab.vue new file mode 100644 index 0000000..0afab66 --- /dev/null +++ b/uni_modules/uni-fab/components/uni-fab/uni-fab.vue @@ -0,0 +1,448 @@ + + + + + diff --git a/uni_modules/uni-fab/components/uni-fab/uni-fab.vue.bak b/uni_modules/uni-fab/components/uni-fab/uni-fab.vue.bak new file mode 100644 index 0000000..9df4dcd --- /dev/null +++ b/uni_modules/uni-fab/components/uni-fab/uni-fab.vue.bak @@ -0,0 +1,383 @@ + + + + + diff --git a/uni_modules/uni-fab/package.json b/uni_modules/uni-fab/package.json new file mode 100644 index 0000000..84cb814 --- /dev/null +++ b/uni_modules/uni-fab/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-fab", + "displayName": "uni-fab 悬浮按钮", + "version": "1.1.0", + "description": "悬浮按钮 fab button ,点击可展开一个图标按钮菜单。", + "keywords": [ + "uni-ui", + "uniui", + "按钮", + "悬浮按钮", + "fab" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} diff --git a/uni_modules/uni-fab/readme.md b/uni_modules/uni-fab/readme.md new file mode 100644 index 0000000..0ed6ce8 --- /dev/null +++ b/uni_modules/uni-fab/readme.md @@ -0,0 +1,91 @@ + + +## Fab 悬浮按钮 +> **组件名:uni-fab** +> 代码块: `uFab` + + +点击可展开一个图形按钮菜单 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 不建议动态修改属性,可能会耗损部分性能。 +> - 展开菜单暂不支持字体图标,使用图片路径时建议使用绝对路径,相对路径可能会有问题。 +> - 选中状态要通过自己控制,如果不希望有选中状态,不处理 `active` 即可。 +> - 展开菜单建议最多显示四个,如果过多对于小屏手机可能会超出屏幕。 + + +### 基本用法 + +在 `template` 中使用组件 + +```html + +``` + + +## API + +### Fab Props + +| 属性名 | 类型 | 默认值 | 说明 | +| :-: | :-: | :-: | :-: | +| pattern | Object | - | 可选样式配置项 | +| horizontal| String | 'left' | 水平对齐方式。`left`:左对齐,`right`:右对齐 | +| vertical | String | 'bottom' | 垂直对齐方式。`bottom`:下对齐,`top`:上对齐 | +| direction | String | 'horizontal' | 展开菜单显示方式。`horizontal`:水平显示,`vertical`:垂直显示 | +| popMenu | Boolean | true | 是否使用弹出菜单 | +| content | Array | - | 展开菜单内容配置项 | + + + +**pattern配置项:** + +| 参数 | 类型 | 默认值 | 说明 | +| :-: | :-: | :-: | :-: | +| color | String | #3c3e49 | 文字默认颜色 | +| selectedColor | String | #007AFF | 文字选中时的颜色 | +| backgroundColor | String | #ffffff | 背景色 | +| buttonColor | String | #3c3e49 | 按钮背景色 | + +**content配置项:** + +| 参数 | 类型 | 说明 | +| :-: | :-: | :-: | :-: | +| iconPath | String | 图片路径 | +| selectedIconPath | String | 选中后图片路径| +| text | String | 文字 | +| active | Boolean | 是否选中当前 | + +### Fab Events + +| 参数 | 类型 | 说明 | +| :-: | :-: | :-: | +| @trigger | Function | 展开菜单点击事件,返回点击信息| +| @fabClick | Function | 悬浮按钮点击事件 | + + + + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/fab/fab](https://hellouniapp.dcloud.net.cn/pages/extUI/fab/fab) \ No newline at end of file diff --git a/uni_modules/uni-fav/changelog.md b/uni_modules/uni-fav/changelog.md new file mode 100644 index 0000000..f35b817 --- /dev/null +++ b/uni_modules/uni-fav/changelog.md @@ -0,0 +1,14 @@ +## 1.1.1(2021-08-24) +- 新增 支持国际化 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.6(2021-05-12) +- 新增 组件示例地址 +## 1.0.5(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.4(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.0.3(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.0.2(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-fav/components/uni-fav/i18n/en.json b/uni_modules/uni-fav/components/uni-fav/i18n/en.json new file mode 100644 index 0000000..9a0759e --- /dev/null +++ b/uni_modules/uni-fav/components/uni-fav/i18n/en.json @@ -0,0 +1,4 @@ +{ + "uni-fav.collect": "collect", + "uni-fav.collected": "collected" +} diff --git a/uni_modules/uni-fav/components/uni-fav/i18n/index.js b/uni_modules/uni-fav/components/uni-fav/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uni_modules/uni-fav/components/uni-fav/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json b/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json new file mode 100644 index 0000000..67c89bf --- /dev/null +++ b/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json @@ -0,0 +1,4 @@ +{ + "uni-fav.collect": "收藏", + "uni-fav.collected": "已收藏" +} diff --git a/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json b/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json new file mode 100644 index 0000000..67c89bf --- /dev/null +++ b/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json @@ -0,0 +1,4 @@ +{ + "uni-fav.collect": "收藏", + "uni-fav.collected": "已收藏" +} diff --git a/uni_modules/uni-fav/components/uni-fav/uni-fav.vue b/uni_modules/uni-fav/components/uni-fav/uni-fav.vue new file mode 100644 index 0000000..d82864b --- /dev/null +++ b/uni_modules/uni-fav/components/uni-fav/uni-fav.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/uni_modules/uni-fav/package.json b/uni_modules/uni-fav/package.json new file mode 100644 index 0000000..0556086 --- /dev/null +++ b/uni_modules/uni-fav/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-fav", + "displayName": "uni-fav 收藏按钮", + "version": "1.1.1", + "description": " Fav 收藏组件,可自定义颜色、大小。", + "keywords": [ + "fav", + "uni-ui", + "uniui", + "收藏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} diff --git a/uni_modules/uni-fav/readme.md b/uni_modules/uni-fav/readme.md new file mode 100644 index 0000000..83a01fc --- /dev/null +++ b/uni_modules/uni-fav/readme.md @@ -0,0 +1,50 @@ + + +## Fav 收藏按钮 +> **组件名:uni-fav** +> 代码块: `uFav` + + +用于收藏功能,可点击切换选中、不选中的状态。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + +``` + +## API + +### Fav Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|star |Boolean|true |按钮是否带星星 | +|bgColor |String |#eeeeee |未收藏时的背景色 | +|bgColorChecked |String |#007aff |已收藏时的背景色 | +|fgColor |String |#666666 |未收藏时的文字颜色 | +|fgColorChecked |String |#FFFFFF |已收藏时的文字颜色 | +|circle |Boolean|false |是否为圆角 | +|checked |Boolean|false |是否为已收藏 | +|contentText |Object |```{contentDefault: '收藏',contentFav: '已收藏'}```|收藏按钮文字 | + + +### Fav Events + +|事件称名 |说明 |返回值 | +|:-: |:-: |:-: | +|click |点击 fav按钮 触发事件 |- | + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/fav/fav](https://hellouniapp.dcloud.net.cn/pages/extUI/fav/fav) \ No newline at end of file diff --git a/uni_modules/uni-feedback/changelog.md b/uni_modules/uni-feedback/changelog.md new file mode 100644 index 0000000..57c13b8 --- /dev/null +++ b/uni_modules/uni-feedback/changelog.md @@ -0,0 +1,2 @@ +## 1.0.3(2021-08-26) +删除多余的云函数test2 diff --git a/uni_modules/uni-feedback/js_sdk/validator/opendb-feedback.js b/uni_modules/uni-feedback/js_sdk/validator/opendb-feedback.js new file mode 100644 index 0000000..be3d330 --- /dev/null +++ b/uni_modules/uni-feedback/js_sdk/validator/opendb-feedback.js @@ -0,0 +1,98 @@ +// 表单校验规则由 schema2code 生成,不建议直接修改校验规则,而建议通过 schema2code 生成, 详情: https://uniapp.dcloud.net.cn/uniCloud/schema + + +const validator = { + "content": { + "rules": [ + { + "required": true + }, + { + "format": "string" + } + ], + "label": "留言内容/回复内容" + }, + "imgs": { + "rules": [ + { + "format": "array" + }, + { + "arrayType": "file" + }, + { + "maxLength": 6 + } + ], + "label": "图片列表" + }, + "contact": { + "rules": [ + { + "format": "string" + } + ], + "label": "联系人" + }, + "mobile": { + "rules": [ + { + "format": "string" + }, + { + "pattern": "^\\+?[0-9-]{3,20}$" + } + ], + "label": "联系电话" + } +} + +const enumConverter = {} + +function filterToWhere(filter, command) { + let where = {} + for (let field in filter) { + let { type, value } = filter[field] + switch (type) { + case "search": + if (typeof value === 'string' && value.length) { + where[field] = new RegExp(value) + } + break; + case "select": + if (value.length) { + let selectValue = [] + for (let s of value) { + selectValue.push(command.eq(s)) + } + where[field] = command.or(selectValue) + } + break; + case "range": + if (value.length) { + let gt = value[0] + let lt = value[1] + where[field] = command.and([command.gte(gt), command.lte(lt)]) + } + break; + case "date": + if (value.length) { + let [s, e] = value + let startDate = new Date(s) + let endDate = new Date(e) + where[field] = command.and([command.gte(startDate), command.lte(endDate)]) + } + break; + case "timestamp": + if (value.length) { + let [startDate, endDate] = value + where[field] = command.and([command.gte(startDate), command.lte(endDate)]) + } + break; + } + } + return where +} + +export { validator, enumConverter, filterToWhere } diff --git a/uni_modules/uni-feedback/package.json b/uni_modules/uni-feedback/package.json new file mode 100644 index 0000000..fb20e7a --- /dev/null +++ b/uni_modules/uni-feedback/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-feedback", + "displayName": "问题反馈用户端页面模板", + "version": "1.0.3", + "description": "问题反馈用户端页面模板,方便开发者快速搭建问题反馈界面", + "keywords": [ + "问题反馈用户端页面模板" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "uniCloud", + "云端一体页面模板" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [ + "uni-forms", + "uni-file-picker", + "uni-easyinput", + "uni-load-more", + "uni-list", + "uni-fab", + "uni-link" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "u", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-feedback/pages/opendb-feedback/detail.vue b/uni_modules/uni-feedback/pages/opendb-feedback/detail.vue new file mode 100644 index 0000000..fa28fca --- /dev/null +++ b/uni_modules/uni-feedback/pages/opendb-feedback/detail.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/uni_modules/uni-feedback/pages/opendb-feedback/edit.vue b/uni_modules/uni-feedback/pages/opendb-feedback/edit.vue new file mode 100644 index 0000000..bd65125 --- /dev/null +++ b/uni_modules/uni-feedback/pages/opendb-feedback/edit.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/uni_modules/uni-feedback/pages/opendb-feedback/list.vue b/uni_modules/uni-feedback/pages/opendb-feedback/list.vue new file mode 100644 index 0000000..d075eb3 --- /dev/null +++ b/uni_modules/uni-feedback/pages/opendb-feedback/list.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/uni_modules/uni-feedback/pages/opendb-feedback/opendb-feedback.vue b/uni_modules/uni-feedback/pages/opendb-feedback/opendb-feedback.vue new file mode 100644 index 0000000..1978c72 --- /dev/null +++ b/uni_modules/uni-feedback/pages/opendb-feedback/opendb-feedback.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/uni_modules/uni-feedback/readme.md b/uni_modules/uni-feedback/readme.md new file mode 100644 index 0000000..aa0ede5 --- /dev/null +++ b/uni_modules/uni-feedback/readme.md @@ -0,0 +1,2 @@ +这是一个问题反馈客户端插件,admin端插件:[https://ext.dcloud.net.cn/plugin?id=4992](https://ext.dcloud.net.cn/plugin?id=4992) +> 参考案例 [uni-starter](https://ext.dcloud.net.cn/plugin?id=5057) \ No newline at end of file diff --git a/uni_modules/uni-feedback/uniCloud/database/opendb-feedback.schema.json b/uni_modules/uni-feedback/uniCloud/database/opendb-feedback.schema.json new file mode 100644 index 0000000..e13d874 --- /dev/null +++ b/uni_modules/uni-feedback/uniCloud/database/opendb-feedback.schema.json @@ -0,0 +1,72 @@ +{ + "bsonType": "object", + "required": ["content"], + "permission": { + "create": "auth.uid != null", + "read": true, + "delete": false, + "update": false + }, + "properties": { + "_id": { + "description": "ID,系统自动生成" + }, + "user_id": { + "bsonType": "string", + "description": "留言反馈用户ID\/回复留言用户ID,参考uni-id-users表", + "foreignKey": "uni-id-users._id", + "forceDefaultValue": { + "$env": "uid" + } + }, + "create_date": { + "bsonType": "timestamp", + "title": "留言时间\/回复留言时间", + "forceDefaultValue": { + "$env": "now" + } + }, + "content": { + "bsonType": "string", + "title": "留言内容\/回复内容", + "componentForEdit": { + "name": "textarea" + }, + "trim": "right" + }, + "imgs": { + "bsonType": "array", + "arrayType": "file", + "maxLength": 6, + "fileMediaType": "image", + "title": "图片列表" + }, + "is_reply": { + "bsonType": "bool", + "title": "是否是回复类型" + }, + "feedback_id": { + "bsonType": "string", + "title": "被回复留言ID" + }, + "contact": { + "bsonType": "string", + "title": "联系人", + "trim": "both" + }, + "mobile": { + "bsonType": "string", + "title": "联系电话", + "pattern": "^\\+?[0-9-]{3,20}$", + "trim": "both" + }, + "reply_count": { + "permission": { + "write": false + }, + "bsonType": "int", + "title": "被回复条数", + "defaultValue": 0 + } + } +} diff --git a/uni_modules/uni-file-picker/changelog.md b/uni_modules/uni-file-picker/changelog.md new file mode 100644 index 0000000..df26c52 --- /dev/null +++ b/uni_modules/uni-file-picker/changelog.md @@ -0,0 +1,52 @@ +## 0.2.14(2021-08-23) +- 新增 参数中返回 fileID 字段 +## 0.2.13(2021-08-23) +- 修复 腾讯云传入fileID 不能回显的bug +- 修复 选择图片后,不能放大的问题 +## 0.2.12(2021-08-17) +- 修复 由于 0.2.11 版本引起的不能回显图片的Bug +## 0.2.11(2021-08-16) +- 新增 clearFiles(index) 方法,可以手动删除指定文件 +- 修复 v-model 值设为 null 报错的Bug +## 0.2.10(2021-08-13) +- 修复 return-type="object" 时,无法删除文件的Bug +## 0.2.9(2021-08-03) +- 修复 auto-upload 属性失效的Bug +## 0.2.8(2021-07-31) +- 修复 fileExtname属性不指定值报错的Bug +## 0.2.7(2021-07-31) +- 修复 在某种场景下图片不回显的Bug +## 0.2.6(2021-07-30) +- 修复 return-type为object下,返回值不正确的Bug +## 0.2.5(2021-07-30) +- 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题 +## 0.2.3(2021-07-28) +- 优化 调整示例代码 +## 0.2.2(2021-07-27) +- 修复 vue3 下赋值错误的Bug +- 优化 h5平台下上传文件导致页面卡死的问题 +## 0.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.1.1(2021-07-02) +- 修复 sourceType 缺少默认值导致 ios 无法选择文件 +## 0.1.0(2021-06-30) +- 优化 解耦与uniCloud的强绑定关系 ,如不绑定服务空间,默认autoUpload为false且不可更改 +## 0.0.11(2021-06-30) +- 修复 由 0.0.10 版本引发的 returnType 属性失效的问题 +## 0.0.10(2021-06-29) +- 优化 文件上传后进度条消失时机 +## 0.0.9(2021-06-29) +- 修复 在uni-forms 中,删除文件 ,获取的值不对的Bug +## 0.0.8(2021-06-15) +- 修复 删除文件时无法触发 v-model 的Bug +## 0.0.7(2021-05-12) +- 新增 组件示例地址 +## 0.0.6(2021-04-09) +- 修复 选择的文件非 file-extname 字段指定的扩展名报错的Bug +## 0.0.5(2021-04-09) +- 优化 更新组件示例 +## 0.0.4(2021-04-09) +- 优化 file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔 +## 0.0.3(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 微信小程序不指定 fileExtname 属性选择失败的Bug diff --git a/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js b/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js new file mode 100644 index 0000000..24a07f5 --- /dev/null +++ b/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js @@ -0,0 +1,224 @@ +'use strict'; + +const ERR_MSG_OK = 'chooseAndUploadFile:ok'; +const ERR_MSG_FAIL = 'chooseAndUploadFile:fail'; + +function chooseImage(opts) { + const { + count, + sizeType = ['original', 'compressed'], + sourceType = ['album', 'camera'], + extension + } = opts + return new Promise((resolve, reject) => { + uni.chooseImage({ + count, + sizeType, + sourceType, + extension, + success(res) { + resolve(normalizeChooseAndUploadFileRes(res, 'image')); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function chooseVideo(opts) { + const { + camera, + compressed, + maxDuration, + sourceType = ['album', 'camera'], + extension + } = opts; + return new Promise((resolve, reject) => { + uni.chooseVideo({ + camera, + compressed, + maxDuration, + sourceType, + extension, + success(res) { + const { + tempFilePath, + duration, + size, + height, + width + } = res; + resolve(normalizeChooseAndUploadFileRes({ + errMsg: 'chooseVideo:ok', + tempFilePaths: [tempFilePath], + tempFiles: [ + { + name: (res.tempFile && res.tempFile.name) || '', + path: tempFilePath, + size, + type: (res.tempFile && res.tempFile.type) || '', + width, + height, + duration, + fileType: 'video', + cloudPath: '', + }, ], + }, 'video')); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function chooseAll(opts) { + const { + count, + extension + } = opts; + return new Promise((resolve, reject) => { + let chooseFile = uni.chooseFile; + if (typeof wx !== 'undefined' && + typeof wx.chooseMessageFile === 'function') { + chooseFile = wx.chooseMessageFile; + } + if (typeof chooseFile !== 'function') { + return reject({ + errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。', + }); + } + chooseFile({ + type: 'all', + count, + extension, + success(res) { + resolve(normalizeChooseAndUploadFileRes(res)); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function normalizeChooseAndUploadFileRes(res, fileType) { + res.tempFiles.forEach((item, index) => { + if (!item.name) { + item.name = item.path.substring(item.path.lastIndexOf('/') + 1); + } + if (fileType) { + item.fileType = fileType; + } + item.cloudPath = + Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.')); + }); + if (!res.tempFilePaths) { + res.tempFilePaths = res.tempFiles.map((file) => file.path); + } + return res; +} + +function uploadCloudFiles(files, max = 5, onUploadProgress) { + files = JSON.parse(JSON.stringify(files)) + const len = files.length + let count = 0 + let self = this + return new Promise(resolve => { + while (count < max) { + next() + } + + function next() { + let cur = count++ + if (cur >= len) { + !files.find(item => !item.url && !item.errMsg) && resolve(files) + return + } + const fileItem = files[cur] + const index = self.files.findIndex(v => v.uuid === fileItem.uuid) + fileItem.url = '' + delete fileItem.errMsg + + uniCloud + .uploadFile({ + filePath: fileItem.path, + cloudPath: fileItem.cloudPath, + fileType: fileItem.fileType, + onUploadProgress: res => { + res.index = index + onUploadProgress && onUploadProgress(res) + } + }) + .then(res => { + fileItem.url = res.fileID + fileItem.index = index + if (cur < len) { + next() + } + }) + .catch(res => { + fileItem.errMsg = res.errMsg || res.message + fileItem.index = index + if (cur < len) { + next() + } + }) + } + }) +} + + + + + +function uploadFiles(choosePromise, { + onChooseFile, + onUploadProgress +}) { + return choosePromise + .then((res) => { + if (onChooseFile) { + const customChooseRes = onChooseFile(res); + if (typeof customChooseRes !== 'undefined') { + return Promise.resolve(customChooseRes).then((chooseRes) => typeof chooseRes === 'undefined' ? + res : chooseRes); + } + } + return res; + }) + .then((res) => { + if (res === false) { + return { + errMsg: ERR_MSG_OK, + tempFilePaths: [], + tempFiles: [], + }; + } + return res + }) +} + +function chooseAndUploadFile(opts = { + type: 'all' +}) { + if (opts.type === 'image') { + return uploadFiles(chooseImage(opts), opts); + } + else if (opts.type === 'video') { + return uploadFiles(chooseVideo(opts), opts); + } + return uploadFiles(chooseAll(opts), opts); +} + +export { + chooseAndUploadFile, + uploadCloudFiles +}; diff --git a/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue b/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue new file mode 100644 index 0000000..ddf1c93 --- /dev/null +++ b/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue @@ -0,0 +1,652 @@ + + + + + diff --git a/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue b/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue new file mode 100644 index 0000000..625d92e --- /dev/null +++ b/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue @@ -0,0 +1,325 @@ + + + + + diff --git a/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue b/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue new file mode 100644 index 0000000..6d6cdc0 --- /dev/null +++ b/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue @@ -0,0 +1,290 @@ + + + + + diff --git a/uni_modules/uni-file-picker/components/uni-file-picker/utils.js b/uni_modules/uni-file-picker/components/uni-file-picker/utils.js new file mode 100644 index 0000000..60aaa3e --- /dev/null +++ b/uni_modules/uni-file-picker/components/uni-file-picker/utils.js @@ -0,0 +1,109 @@ +/** + * 获取文件名和后缀 + * @param {String} name + */ +export const get_file_ext = (name) => { + const last_len = name.lastIndexOf('.') + const len = name.length + return { + name: name.substring(0, last_len), + ext: name.substring(last_len + 1, len) + } +} + +/** + * 获取扩展名 + * @param {Array} fileExtname + */ +export const get_extname = (fileExtname) => { + if (!Array.isArray(fileExtname)) { + let extname = fileExtname.replace(/(\[|\])/g, '') + return extname.split(',') + } else { + return fileExtname + } + return [] +} + +/** + * 获取文件和检测是否可选 + */ +export const get_files_and_is_max = (res, _extname) => { + let filePaths = [] + let files = [] + if(!_extname || _extname.length === 0){ + return { + filePaths, + files + } + } + res.tempFiles.forEach(v => { + let fileFullName = get_file_ext(v.name) + const extname = fileFullName.ext.toLowerCase() + if (_extname.indexOf(extname) !== -1) { + files.push(v) + filePaths.push(v.path) + } + }) + if (files.length !== res.tempFiles.length) { + uni.showToast({ + title: `当前选择了${res.tempFiles.length}个文件 ,${res.tempFiles.length - files.length} 个文件格式不正确`, + icon: 'none', + duration: 5000 + }) + } + + return { + filePaths, + files + } +} + + +/** + * 获取图片信息 + * @param {Object} filepath + */ +export const get_file_info = (filepath) => { + return new Promise((resolve, reject) => { + uni.getImageInfo({ + src: filepath, + success(res) { + resolve(res) + }, + fail(err) { + reject(err) + } + }) + }) +} +/** + * 获取封装数据 + */ +export const get_file_data = async (files, type = 'image') => { + // 最终需要上传数据库的数据 + let fileFullName = get_file_ext(files.name) + const extname = fileFullName.ext.toLowerCase() + let filedata = { + name: files.name, + uuid: files.uuid, + extname: extname || '', + cloudPath: files.cloudPath, + fileType: files.fileType, + url: files.path || files.path, + size: files.size, //单位是字节 + image: {}, + path: files.path, + video: {} + } + if (type === 'image') { + const imageinfo = await get_file_info(files.path) + delete filedata.video + filedata.image.width = imageinfo.width + filedata.image.height = imageinfo.height + filedata.image.location = imageinfo.path + } else { + delete filedata.image + } + return filedata +} diff --git a/uni_modules/uni-file-picker/package.json b/uni_modules/uni-file-picker/package.json new file mode 100644 index 0000000..0a02dda --- /dev/null +++ b/uni_modules/uni-file-picker/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-file-picker", + "displayName": "uni-file-picker 文件选择上传", + "version": "0.2.14", + "description": "文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间", + "keywords": [ + "uni-ui", + "uniui", + "图片上传", + "文件上传" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} diff --git a/uni_modules/uni-file-picker/readme.md b/uni_modules/uni-file-picker/readme.md new file mode 100644 index 0000000..496327b --- /dev/null +++ b/uni_modules/uni-file-picker/readme.md @@ -0,0 +1,305 @@ + +## FilePicker 文件选择上传 + +> **组件名:uni-file-picker** +> 代码块: `uFilePicker` + + +文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间 + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - 如不绑定服务空间,`autoUpload`默认为`false`且不可更改 +> - 选择文件目前只支持 `H5` 和 `微信小程序平台` ,且 `微信小程序平台` 使用 `wx.chooseMessageFile()` +> - v-model 值需要自动上传成功后才会绑定值,一般只用来回显数据 +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + +## API + +### FilePicker Props + +| 属性名 | 类型 | 默认值 | 可选值 | 说明 | +| :-: | :-: | :-: | :-: | :-: | +| v-model/value | Array\Object | - | - | 组件数据,通常用来回显 ,类型由`return-type`属性决定 ,**格式见下文** | +| disabled | Boolean | false | - | 组件禁用 | +| readonly | Boolean | false | - | 组件只读,不可选择,不显示进度,不显示删除按钮 | +| return-type | String | array | array/object | 限制 `value` 格式,当为 `object` 时 ,组件只能单选,且会覆盖 | +| disable-preview| Boolean | false | - | 禁用图片预览,仅 `mode:grid`生效 | +| del-icon | Boolean | true | - | 是否显示删除按钮 | +| auto-upload | Boolean | true | - | 是否自动上传,值为`true`则只触发@select,可自行上传| +| limit | Number\String | 9 | - | 最大选择个数 ,h5 会自动忽略多选的部分 | +| title | String | - | - | 组件标题,右侧显示上传计数 | +| mode | String | list | list/grid | 选择文件后的文件列表样式 | +| file-mediatype| String | image | image/video/all | 选择文件类型,all 只支持 H5 和微信小程序平台 | +| file-extname | Array\String | - | - | 选择文件后缀,字符串的情况下需要用逗号分隔(推荐使用字符串),根据 `file-mediatype` 属性而不同| +| list-styles | Object | - | - | `mode:list` 时的样式 | +| image-styles | Object | - | - | `mode:grid` 时的样式 | + + +### value 格式 + +三个属性必填,否则影响组件显示 + +```json +[ + { + "name":"file.txt", + "extname":"txt", + "url":"https://xxxx", + // ... + } +] + +``` + +### list-styles 格式 + +```json +{ + "borderStyle":{ + "color":"#eee", // 边框颜色 + "width":"1px", // 边框宽度 + "style":"solid", // 边框样式 + "radius":"5px" // 边框圆角,不支持百分比 + }, + "border":false, // 是否显示边框 + "dividline":true // 是否显示分隔线 +} +``` + +### image-styles 格式 + +```json +{ + "height": 60, // 边框高度 + "width": 60, // 边框宽度 + "border":{ // 如果为 Boolean 值,可以控制边框显示与否 + "color":"#eee", // 边框颜色 + "width":"1px", // 边框宽度 + "style":"solid", // 边框样式 + "radius":"50%" // 边框圆角,支持百分比 + } +} +``` + +### FilePicker Events + +|事件称名 |说明 | 返回值 | +|:-: |:-: | :-: | +|@select | 选择文件后触发 | 见下文| +|@progress|文件上传时触发 | 见下文| +|@success |上传成功触发 | 见下文| +|@fail |上传失败触发 | 见下文| +|@delete |文件从列表移除时触发| 见下文| + + +#### Callback Params + +`**注意**:如果绑定的是腾讯云的服务空间 ,tempFilePaths 将返回 fileID` + +```json +{ + "progress" : Number, // 上传进度 ,仅 @progress 事件包含此字段 + "index" : Number, // 上传文件索引 ,仅 @progress 事件包含此字段 + "tempFile" : file, // 当前文件对象 ,包含文件流,文件大小,文件名称等,仅 @progress 事件包含此字段 + "tempFiles" : files, // 文件列表,包含文件流,文件大小,文件名称等 + "tempFilePaths" : filePaths, // 文件地址列表,@sucess 事件为上传后的线上文件地址 +} + +``` + + +### FilePicker Methods + +通过 `$ref` 调用 + +| 方法称名 | 说明 | 参数 | +| :-: | :-: | :-: | +| upload() | 手动上传 ,如`autoUpload`为`false` ,必须调用此方法| - | +| clearFiles(index:Number) | 清除选择结果| 传如 Number 为删除指定下标的文件 ,不传为删除所有| + +### FilePicker Slots + +插槽可定义上传按钮显示样式 + +| 插槽名 | 说明 | +| :-: | :-: | +| default |默认插槽| + +## 组件用法 + +### 基础用法 + +```html + +``` + +```javascript +export default { + data() { + return { + imageValue:[] + } + }, + methods:{ + // 获取上传状态 + select(e){ + console.log('选择文件:',e) + }, + // 获取上传进度 + progress(e){ + console.log('上传进度:',e) + }, + + // 上传成功 + success(e){ + console.log('上传成功') + }, + + // 上传失败 + fail(e){ + console.log('上传失败:',e) + } + } +} + +``` + +### 选择指定后缀图片,且限制选择个数 + +配置 `file-mediatype` 属性为 `image`,限定只选择图片 + +配置 `file-extname` 属性为 `'png,jpg'`,限定只选择 `png`和`jpg`后缀的图片 + +配置 `limit` 属性为 1 ,则最多选择一张图片 + +配置 `mode` 属性为 `grid` ,可以使用九宫格样式选择图片 + + +```html + +``` + +### 手动上传 + +配置 `auto-upload` 属性为 `false` ,可以停止自动上传,通过`ref`调用`upload`方法自行选择上传时机 + +```html + + + + +``` + +```javascript +export default { + data() {}, + methods:{ + upload(){ + this.$refs.files.upload() + } + } +} + +``` + +### 单选图片且点击再次选择 + +配置 `disable-preview` 属性为 `true`,禁用点击预览图片功能 + +配置 `del-icon` 属性为 `false`,隐藏删除按钮 + +配置 `return-type` 属性为 `object`,设置 `value` 类型 ,如需绑定 `array`类型 ,则设置`limit:1`,可达到一样的效果 + + + +```html +选择头像 +``` + +### 自定义样式 + +配置 `image-styles` 属性,可以自定义`mode:image`时的回显样式 + +配置 `list-styles` 属性,可以自定义`mode:video|| mode:all`时的回显样式 + +```html + + + + +``` + +```javascript +export default { + data() { + imageStyles:{ + width:64, + height:64, + border:{ + color:"#ff5a5f", + width:2, + style:'dashed', + radius:'2px' + } + }, + listStyles:{ + // 是否显示边框 + border: true, + // 是否显示分隔线 + dividline: true, + // 线条样式 + borderStyle: { + width:1, + color:'blue', + radius:2 + } + } + } +} + +``` + + + +### 使用插槽 + +使用默认插槽可以自定义选择文件按钮样式 + +```html + + + +``` + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/file-picker/file-picker](https://hellouniapp.dcloud.net.cn/pages/extUI/file-picker/file-picker) \ No newline at end of file diff --git a/uni_modules/uni-forms/changelog.md b/uni_modules/uni-forms/changelog.md new file mode 100644 index 0000000..b5b12f0 --- /dev/null +++ b/uni_modules/uni-forms/changelog.md @@ -0,0 +1,53 @@ +## 1.2.7(2021-08-13) +- 修复 没有添加校验规则的字段依然报错的Bug +## 1.2.6(2021-08-11) +- 修复 重置表单错误信息无法清除的问题 +## 1.2.5(2021-08-11) +- 优化 组件文档 +## 1.2.4(2021-08-11) +- 修复 表单验证只生效一次的问题 +## 1.2.3(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.2.2(2021-07-26) +- 修复 vue2 下条件编译导致destroyed生命周期失效的Bug +- 修复 1.2.1 引起的示例在小程序平台报错的Bug +## 1.2.1(2021-07-22) +- 修复 动态校验表单,默认值为空的情况下校验失效的Bug +- 修复 不指定name属性时,运行报错的Bug +- 优化 label默认宽度从65调整至70,使required为true且四字时不换行 +- 优化 组件示例,新增动态校验示例代码 +- 优化 组件文档,使用方式更清晰 +## 1.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.2(2021-06-25) +- 修复 pattern 属性在微信小程序平台无效的问题 +## 1.1.1(2021-06-22) +- 修复 validate-trigger属性为submit且err-show-type属性为toast时不能弹出的Bug +## 1.1.0(2021-06-22) +- 修复 只写setRules方法而导致校验不生效的Bug +- 修复 由上个办法引发的错误提示文字错位的Bug +## 1.0.48(2021-06-21) +- 修复 不设置 label 属性 ,无法设置label插槽的问题 +## 1.0.47(2021-06-21) +- 修复 不设置label属性,label-width属性不生效的bug +- 修复 setRules 方法与rules属性冲突的问题 +## 1.0.46(2021-06-04) +- 修复 动态删减数据导致报错的问题 +## 1.0.45(2021-06-04) +- 新增 modelValue 属性 ,value 即将废弃 +## 1.0.44(2021-06-02) +- 新增 uni-forms-item 可以设置单独的 rules +- 新增 validate 事件增加 keepitem 参数,可以选择那些字段不过滤 +- 优化 submit 事件重命名为 validate +## 1.0.43(2021-05-12) +- 新增 组件示例地址 +## 1.0.42(2021-04-30) +- 修复 自定义检验器失效的问题 +## 1.0.41(2021-03-05) +- 更新 校验器 +- 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug +## 1.0.40(2021-03-04) +- 修复 动态显示uni-forms-item的情况下,submit 方法获取值错误的Bug +## 1.0.39(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 校验器传入 int 等类型 ,返回String类型的Bug diff --git a/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue b/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue new file mode 100644 index 0000000..5837320 --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue @@ -0,0 +1,509 @@ + + + + + diff --git a/uni_modules/uni-forms/components/uni-forms/uni-forms.vue b/uni_modules/uni-forms/components/uni-forms/uni-forms.vue new file mode 100644 index 0000000..057afc8 --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms/uni-forms.vue @@ -0,0 +1,472 @@ + + + + + diff --git a/uni_modules/uni-forms/components/uni-forms/validate.js b/uni_modules/uni-forms/components/uni-forms/validate.js new file mode 100644 index 0000000..1834c6c --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms/validate.js @@ -0,0 +1,486 @@ +var pattern = { + email: /^\S+?@\S+?\.\S+?$/, + idcard: /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, + 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') +}; + +const FORMAT_MAPPING = { + "int": 'integer', + "bool": 'boolean', + "double": 'number', + "long": 'number', + "password": 'string' + // "fileurls": 'array' +} + +function formatMessage(args, resources = '') { + var defaultMessage = ['label'] + defaultMessage.forEach((item) => { + if (args[item] === undefined) { + args[item] = '' + } + }) + + let str = resources + for (let key in args) { + let reg = new RegExp('{' + key + '}') + str = str.replace(reg, args[key]) + } + return str +} + +function isEmptyValue(value, type) { + if (value === undefined || value === null) { + return true; + } + + if (typeof value === 'string' && !value) { + return true; + } + + if (Array.isArray(value) && !value.length) { + return true; + } + + if (type === 'object' && !Object.keys(value).length) { + return true; + } + + return false; +} + +const types = { + integer(value) { + return types.number(value) && parseInt(value, 10) === value; + }, + string(value) { + return typeof value === 'string'; + }, + number(value) { + if (isNaN(value)) { + return false; + } + return typeof value === 'number'; + }, + "boolean": function(value) { + return typeof value === 'boolean'; + }, + "float": function(value) { + return types.number(value) && !types.integer(value); + }, + array(value) { + return Array.isArray(value); + }, + object(value) { + return typeof value === 'object' && !types.array(value); + }, + date(value) { + return value instanceof Date; + }, + timestamp(value) { + if (!this.integer(value) || Math.abs(value).toString().length > 16) { + return false + } + return true; + }, + file(value) { + return typeof value.url === 'string'; + }, + email(value) { + return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255; + }, + url(value) { + return typeof value === 'string' && !!value.match(pattern.url); + }, + pattern(reg, value) { + try { + return new RegExp(reg).test(value); + } catch (e) { + return false; + } + }, + method(value) { + return typeof value === 'function'; + }, + idcard(value) { + return typeof value === 'string' && !!value.match(pattern.idcard); + }, + 'url-https'(value) { + return this.url(value) && value.startsWith('https://'); + }, + 'url-scheme'(value) { + return value.startsWith('://'); + }, + 'url-web'(value) { + return false; + } +} + +class RuleValidator { + + constructor(message) { + this._message = message + } + + async validateRule(fieldKey, fieldValue, value, data, allData) { + var result = null + + let rules = fieldValue.rules + + let hasRequired = rules.findIndex((item) => { + return item.required + }) + if (hasRequired < 0) { + if (value === null || value === undefined) { + return result + } + if (typeof value === 'string' && !value.length) { + return result + } + } + + var message = this._message + + if (rules === undefined) { + return message['default'] + } + + for (var i = 0; i < rules.length; i++) { + let rule = rules[i] + let vt = this._getValidateType(rule) + + Object.assign(rule, { + label: fieldValue.label || `["${fieldKey}"]` + }) + + if (RuleValidatorHelper[vt]) { + result = RuleValidatorHelper[vt](rule, value, message) + if (result != null) { + break + } + } + + if (rule.validateExpr) { + let now = Date.now() + let resultExpr = rule.validateExpr(value, allData, now) + if (resultExpr === false) { + result = this._getMessage(rule, rule.errorMessage || this._message['default']) + break + } + } + + if (rule.validateFunction) { + result = await this.validateFunction(rule, value, data, allData, vt) + if (result !== null) { + break + } + } + } + + if (result !== null) { + result = message.TAG + result + } + + return result + } + + async validateFunction(rule, value, data, allData, vt) { + let result = null + try { + let callbackMessage = null + const res = await rule.validateFunction(rule, value, allData || data, (message) => { + callbackMessage = message + }) + if (callbackMessage || (typeof res === 'string' && res) || res === false) { + result = this._getMessage(rule, callbackMessage || res, vt) + } + } catch (e) { + result = this._getMessage(rule, e.message, vt) + } + return result + } + + _getMessage(rule, message, vt) { + return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default']) + } + + _getValidateType(rule) { + var result = '' + if (rule.required) { + result = 'required' + } else if (rule.format) { + result = 'format' + } else if (rule.arrayType) { + result = 'arrayTypeFormat' + } else if (rule.range) { + result = 'range' + } else if (rule.maximum !== undefined || rule.minimum !== undefined) { + result = 'rangeNumber' + } else if (rule.maxLength !== undefined || rule.minLength !== undefined) { + result = 'rangeLength' + } else if (rule.pattern) { + result = 'pattern' + } else if (rule.validateFunction) { + result = 'validateFunction' + } + return result + } +} + +const RuleValidatorHelper = { + required(rule, value, message) { + if (rule.required && isEmptyValue(value, rule.format || typeof value)) { + return formatMessage(rule, rule.errorMessage || message.required); + } + + return null + }, + + range(rule, value, message) { + const { + range, + errorMessage + } = rule; + + let list = new Array(range.length); + for (let i = 0; i < range.length; i++) { + const item = range[i]; + if (types.object(item) && item.value !== undefined) { + list[i] = item.value; + } else { + list[i] = item; + } + } + + let result = false + if (Array.isArray(value)) { + result = (new Set(value.concat(list)).size === list.length); + } else { + if (list.indexOf(value) > -1) { + result = true; + } + } + + if (!result) { + return formatMessage(rule, errorMessage || message['enum']); + } + + return null + }, + + rangeNumber(rule, value, message) { + if (!types.number(value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + let { + minimum, + maximum, + exclusiveMinimum, + exclusiveMaximum + } = rule; + let min = exclusiveMinimum ? value <= minimum : value < minimum; + let max = exclusiveMaximum ? value >= maximum : value > maximum; + + if (minimum !== undefined && min) { + return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMinimum ? + 'exclusiveMinimum' : 'minimum' + ]) + } else if (maximum !== undefined && max) { + return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMaximum ? + 'exclusiveMaximum' : 'maximum' + ]) + } else if (minimum !== undefined && maximum !== undefined && (min || max)) { + return formatMessage(rule, rule.errorMessage || message['number'].range) + } + + return null + }, + + rangeLength(rule, value, message) { + if (!types.string(value) && !types.array(value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + let min = rule.minLength; + let max = rule.maxLength; + let val = value.length; + + if (min !== undefined && val < min) { + return formatMessage(rule, rule.errorMessage || message['length'].minLength) + } else if (max !== undefined && val > max) { + return formatMessage(rule, rule.errorMessage || message['length'].maxLength) + } else if (min !== undefined && max !== undefined && (val < min || val > max)) { + return formatMessage(rule, rule.errorMessage || message['length'].range) + } + + return null + }, + + pattern(rule, value, message) { + if (!types['pattern'](rule.pattern, value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + return null + }, + + format(rule, value, message) { + var customTypes = Object.keys(types); + var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : (rule.format || rule.arrayType); + + if (customTypes.indexOf(format) > -1) { + if (!types[format](value)) { + return formatMessage(rule, rule.errorMessage || message.typeError); + } + } + + return null + }, + + arrayTypeFormat(rule, value, message) { + if (!Array.isArray(value)) { + return formatMessage(rule, rule.errorMessage || message.typeError); + } + + for (let i = 0; i < value.length; i++) { + const element = value[i]; + let formatResult = this.format(rule, element, message) + if (formatResult !== null) { + return formatResult + } + } + + return null + } +} + +class SchemaValidator extends RuleValidator { + + constructor(schema, options) { + super(SchemaValidator.message); + + this._schema = schema + this._options = options || null + } + + updateSchema(schema) { + this._schema = schema + } + + async validate(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidate(data, false, allData) + } + return result.length ? result[0] : null + } + + async validateAll(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidate(data, true, allData) + } + return result + } + + async validateUpdate(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidateUpdate(data, false, allData) + } + return result.length ? result[0] : null + } + + async invokeValidate(data, all, allData) { + let result = [] + let schema = this._schema + for (let key in schema) { + let value = schema[key] + let errorMessage = await this.validateRule(key, value, data[key], data, allData) + if (errorMessage != null) { + result.push({ + key, + errorMessage + }) + if (!all) break + } + } + return result + } + + async invokeValidateUpdate(data, all, allData) { + let result = [] + for (let key in data) { + let errorMessage = await this.validateRule(key, this._schema[key], data[key], data, allData) + if (errorMessage != null) { + result.push({ + key, + errorMessage + }) + if (!all) break + } + } + return result + } + + _checkFieldInSchema(data) { + var keys = Object.keys(data) + var keys2 = Object.keys(this._schema) + if (new Set(keys.concat(keys2)).size === keys2.length) { + return '' + } + + var noExistFields = keys.filter((key) => { + return keys2.indexOf(key) < 0; + }) + var errorMessage = formatMessage({ + field: JSON.stringify(noExistFields) + }, SchemaValidator.message.TAG + SchemaValidator.message['defaultInvalid']) + return [{ + key: 'invalid', + errorMessage + }] + } +} + +function Message() { + return { + TAG: "", + default: '验证错误', + defaultInvalid: '提交的字段{field}在数据库中并不存在', + validateFunction: '验证无效', + required: '{label}必填', + 'enum': '{label}超出范围', + timestamp: '{label}格式无效', + whitespace: '{label}不能为空', + typeError: '{label}类型无效', + date: { + format: '{label}日期{value}格式无效', + parse: '{label}日期无法解析,{value}无效', + invalid: '{label}日期{value}无效' + }, + length: { + minLength: '{label}长度不能少于{minLength}', + maxLength: '{label}长度不能超过{maxLength}', + range: '{label}必须介于{minLength}和{maxLength}之间' + }, + number: { + minimum: '{label}不能小于{minimum}', + maximum: '{label}不能大于{maximum}', + exclusiveMinimum: '{label}不能小于等于{minimum}', + exclusiveMaximum: '{label}不能大于等于{maximum}', + range: '{label}必须介于{minimum}and{maximum}之间' + }, + pattern: { + mismatch: '{label}格式不匹配' + } + }; +} + + +SchemaValidator.message = new Message(); + +export default SchemaValidator diff --git a/uni_modules/uni-forms/package.json b/uni_modules/uni-forms/package.json new file mode 100644 index 0000000..9d949b2 --- /dev/null +++ b/uni_modules/uni-forms/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-forms", + "displayName": "uni-forms 表单", + "version": "1.2.7", + "description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据", + "keywords": [ + "uni-ui", + "表单", + "校验", + "表单校验", + "表单验证" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} diff --git a/uni_modules/uni-forms/readme.md b/uni_modules/uni-forms/readme.md new file mode 100644 index 0000000..e835262 --- /dev/null +++ b/uni_modules/uni-forms/readme.md @@ -0,0 +1,830 @@ + + +## Forms 表单 + +> **组件名:uni-forms** +> 代码块: `uForms`、`uni-forms-item` +> 关联组件:`uni-forms-item`、`uni-easyinput`、`uni-data-checkbox`、`uni-group`。 + + +uni-app的内置组件已经有了 `
`组件,用于提交表单内容。 + +然而几乎每个表单都需要做表单验证,为了方便做表单验证,减少重复开发,`uni ui` 又基于 ``组件封装了 ``组件,内置了表单验证功能。 + +`` 提供了 `rules`属性来描述校验规则、``子组件来包裹具体的表单项,以及给原生或三方组件提供了 `binddata()` 来设置表单值。 + +每个要校验的表单项,不管input还是checkbox,都必须放在``组件中,且一个``组件只能放置一个表单项。 + +``组件内部预留了显示error message的区域,默认是在表单项的底部。 + +另外,``组件下面的各个表单项,可以通过``包裹为不同的分组。同一``下的不同表单项目将聚拢在一起,同其他group保持垂直间距。``仅影响视觉效果。 + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - `resetFields` 方法不会重置原生组件和三方组件的值 +> - 如果配置 `validateTrigger` 属性为 `bind` 且表单域组件使用 `input` 事件触发会耗损部分性能,请谨慎使用 +> - 组件支持 nvue ,需要在 `manifest.json > app-plus` 节点下配置 `"nvueStyleCompiler" : "uni-app"` +> - uni-forms 中不包含其他表单组件,如需使用 uni-easyinput、uni-data-checkbox 等组件,需要自行引入 +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +`uni-forms` 组件通常用来做表单校验和提交。每一个 `uni-forms-item` 是它的一个表单域组件,用来承载表单具体内容,`uni-forms-item` 中可以嵌套 `uni-easyinput`、`uni-data-checkbox` 和 uni-app内置的表单组件 ,不过 uni-app 的内置表单组件需要通过 `binddata` 或者 `uni-forms` 提供的 `setValue` 方法,将内容与 `uni-forms` 关联,才可完成表单的校验与提交(详见后文`表单校验` 部分) + +```html + +``` + +### 对齐方式 + +使用 `label-position` 属性可以设置所有表单域的位置,默认在左侧 + +```html + +``` + +## 表单校验 + +表单校验还可以直接通过 `uniCloud web 控制台` 快速根据 `schema` 自动生成表单维护界面,比如新建页面和编辑页面,自动处理校验规则,更多参考[DB Schema](https://uniapp.dcloud.io/uniCloud/schema) + +### 如何使用 + +1. `uni-forms` 需要通过 `rules` 属性传入约定的校验规则,详细描述下文`校验规则说明`。 +```html + + + ... + +``` + +2. `uni-forms` 需要绑定`modelValue`属性,值为表单的key\value 组成的对象。 +```html + + + ... + +``` + +3. `uni-forms-item` 需要设置 `name` 属性为当前字段名,字段为 `String` 类型而非变量。 +```html + + + + + + + + + +``` + +4. 如果使用`uni-easyinput` 和 `uni-data-checkbox` 等关联组件,只需绑定 v-model,无需其他操作。 +5. 如果使用原生 input、checkbox 或三方组件等,只需要给组件绑定 `binddata` 方法即可触发表单校验,无需绑定事件到 `methods` 中,见下方完整示例。 +6. `binddata('name',$event.detail.value,'form')"` 方法接受三个值, + - 第一个参数传入当前表单组件所在的 name,同当前父组件 `uni-forms-item` 绑定属性 `name` 的值 + - 第二个参数传入需要校验的值,内置组件可以通过 `$event.detail.value` 获取到组件的返回值,自定义组件传入需要校验的值即可 + - 第三个参数传入 `uni-forms` 组件绑定属性 `ref` 的值,通常在多表单的时候需要传入,用来区分表单,如页面中仅有一个 `uni-forms` 可忽略 +7. 如果内置 `binddata` 方法无法满足需求,在当前页面的 `methods` 中复写此方法即可,复写此方法需要调用 `uni-forms` 的 `setValue` 来触发表单校验,见下方 `setValue`方法说明 + +**完整示例** + +```html + + +``` + +```javascript +export default { + data() { + return { + // 表单数据 + formData: { + name: 'LiMing', + email: 'dcloud@email.com' + }, + rules: { + // 对name字段进行必填验证 + name: { + rules: [{ + required: true, + errorMessage: '请输入姓名', + }, + { + minLength: 3, + maxLength: 5, + errorMessage: '姓名长度在 {minLength} 到 {maxLength} 个字符', + } + ] + }, + // 对email字段进行必填验证 + email: { + rules: [{ + format: 'email', + errorMessage: '请输入正确的邮箱地址', + }] + } + } + } + }, + methods: { + /** + * 复写 binddata 方法,如果只是为了校验,无复杂自定义操作,可忽略此方法 + * @param {String} name 字段名称 + * @param {String} value 表单域的值 + */ + // binddata(name,value){ + // 通过 input 事件设置表单指定 name 的值 + // this.$refs.form.setValue(name, value) + // }, + // 触发提交表单 + submit() { + this.$refs.form.validate().then(res=>{ + console.log('表单数据信息:', res); + }).catch(err =>{ + console.log('表单错误信息:', err); + }) + } + } +} +``` + +> **注意** +> `modelValue` 对象目前有比较严格的格式要求: +> - 尽量不要使用嵌套的数据结构,因为表单域指定的`name`值与 modeValue 的 key 是一一对应的,只有一种情况例外,那就是动态校验表单,见下方`动态校验表单`章节 + + +### 校验规则说明 + +校验规则接受一个 `Object` 类型的值,通过传入不同的规则来表示每个表单域的值该如何校验 + +对象的 `key` 表示当前表单域的字段名,`value` 为具体的校验规则 + +以下为 `value` 所包含的内容: + +|属性名|类型|说明| +|:-:|:-:|:-:| +|rules|Array|校验规则,见下方 `rules 属性说明`| +|validateTrigger| String| 表单校验时机| +|label|String|当前表单域的字段中文名,多用于 `errorMessage` 的显示,可不填| + + +```javascript +rules: { + // 对name字段进行必填验证 + name: { + // name 字段的校验规则 + rules:[ + // 校验 name 不能为空 + { + required: true, + message: '请填写姓名', + }, + // 对name字段进行长度验证 + { + minLength: 3, + maxLength: 5, + message: '{label}长度在 {minLength} 到 {maxLength} 个字符', + } + ], + // 当前表单域的字段中文名,可不填写 + label:'姓名', + validateTrigger:'submit' + } +} + +``` + + +### rules 属性说明 +每一个验证规则中,可以配置多个属性,下面是一些常见规则属性。实际上这里的规范,与uniCloud的[DB Schema](https://uniapp.dcloud.io/uniCloud/schema?id=validator)规范相同。 + +|属性名|类型|默认值|可选值|说明 | +|:-:|:-:|:-:|:-:|:-:| +|required|Boolean|-|-|是否必填,配置此参数不会显示输入框左边的必填星号,如需要,请配置`uni-forms-item`组件的的required为true| +|range|Array|-|-|数组至少要有一个元素,且数组内的每一个元素都是唯一的。 | +|format|String|-|-|内置校验规则,如这些规则无法满足需求,可以使用正则匹配或者自定义规则 | +|pattern|RegExp|-|-|正则表达式,注意事项见下方说明| +|maximum|Number|-|-| 校验最大值(大于)| +|minimum|Number|-|-| 校验最小值(小于) | +|maxLength|Number|-|-| 校验数据最大长度 | +|errorMessage|String|-|-|校验失败提示信息语,可添加属性占位符,当前表格内属性都可用作占位符| +|trigger|String|bind| bind/submit|校验触发时机| +|validateFunction|Function|-|-|自定义校验规则 | + + +**format属性值说明** + +|属性名|说明| +|:-:|:-:| +|string|必须是 string 类型,默认类型| +|number|必须是 number 类型| +|boolean|必须是 boolean 类型| +|array|必须是 array 类型| +|object|必须是 object 类型| +|url|必须是 url 类型| +|email|必须是 email 类型| + + +> **pattern属性说明** +> 在小程序中,json 中不能使用正则对象,如:`/^\S+?@\S+?\.\S+?$/`,使用正则对象会被微信序列化,导致正则失效。 +> 所以建议统一使用字符串的方式来使用正则 ,如`'^\\S+?@\\S+?\\.\\S+?$'` ,需要注意 `\` 需要使用 `\\` 来转译。 +> 如验证邮箱:/^\S+?@\S+?\.\S+?$/ (注意不带引号),或使用 "^\\S+?@\\S+?\\.\\S+?$"(注意带引号需要使用 `\` 转义) + + + +### validateFunction 自定义校验规则使用说明 +`uni-forms` 的 `rules` 基础规则有时候不能满足项目的所有使用场景,这时候可以使用 `validateFunction` 来自定义校验规则 + +`validateFunction` 方法返回四个参数 `validateFunction:function(rule,value,data,callback){}` ,当然返回参数名开发者可以自定义: + - rule : 当前校验字段在 rules 中所对应的校验规则 + - value : 当前校验字段的值 + - data : 所有校验字段的字段和值的对象 + - callback : 校验完成时的回调,一般无需执行callback,返回true(校验通过)或者false(校验失败)即可 ,如果需要显示不同的 `errMessage`,如果校验不通过需要执行 callback('提示错误信息'),如果校验通过,执行callback()即可 + + +> **注意** +> 需要注意,如果需要使用 `validateFunction` 自定义校验规则,则不能采用 `uni-forms` 的 `rules` 属性来配置校验规则,这时候需要通过`ref`,在`onReady`生命周期调用组件的`setRules`方法绑定验证规则 +> 无法通过props传递变量,是因为微信小程序会过滤掉对象中的方法,导致自定义验证规则无效。 +> + + + + + +```html + + +``` + +```javascript +export default { + data() { + return { + formData:{ + + }, + rules: { + hobby: { + rules: [{ + required: true, + errorMessage: '请选择兴趣', + },{ + validateFunction:function(rule,value,data,callback){ + if (value.length < 2) { + callback('请至少勾选两个兴趣爱好') + } + return true + } + }] + } + } + } + }, + onReady() { + // 需要在onReady中设置规则 + this.$refs.form.setRules(this.rules) + }, + methods: { + submit(form) { + this.$refs.form.validate().then(res=>{ + console.log('表单数据信息:', res); + }).catch(err =>{ + console.log('表单错误信息:', err); + }) + } + } +} + +``` + + +### validateFunction 异步校验 + +上面的自定义校验方式为同步校验 ,如果需要异步校验,`validateFunction` 需要返回一个 `Promise` ,校验不通过 执行 `reject(new Error('错误信息'))` 返回对应的错误信息,如果校验通过则直接执行 `resolve()` 即可,在异步校验方法中,不需要使用 `callback` 。 + +```html + + +``` + +```javascript +export default { + data() { + return { + formData:{ + age:'' + }, + rules: { + age: { + rules: [{ + required: true, + errorMessage: '请输入年龄', + },{ + validateFunction: (rule, value, data, callback) => { + // 异步需要返回 Promise 对象 + return new Promise((resolve, reject) => { + setTimeout(() => { + if (value > 10 ) { + // 通过返回 resolve + resolve() + } else { + // 不通过返回 reject(new Error('错误信息')) + reject(new Error('年龄必须大于十岁')) + } + }, 2000) + }) + } + }] + } + } + } + }, + onReady() { + // 需要在onReady中设置规则 + this.$refs.form.setRules(this.rules) + }, + methods: { + /** + * 表单提交 + * @param {Object} event + */ + submit() { + uni.showLoading() + this.$refs.form.validate().then(res => { + uni.hideLoading() + console.log('表单数据信息:', res); + }).catch(err => { + uni.hideLoading() + console.log('表单错误信息:', err); + }) + } + } +} + +``` + + +### 动态表单校验 + +`uni-forms v1.0.44` 开始增加了动态校验表单的相关内容。 + +多用于同一个字段需要添加多次的场景,如需要动态创建多个域名参与检验。 + +1. 在 `formData` 中定义个变量用来接受同一个字段的多个结果。 +```javascript +dynamicFormData: { + email: '', + // domains 字段下会有多个结果 + domains: {} +} +``` + + +2. 使用 `uni-forms-item` 的 `rules` 属性定义单个表单域的校验规则。 +```html + + ... + +``` + +3. `name` 需要动态指定,格式为: `字段[唯一值]` +```html + + ... + +``` + +4. 需要绑定值的组件的 v-model 也需要动态指定,格式为:`数据源.字段[唯一值]` +```html + + + +``` + +**完整示例** + +```html + + + + + + + + + + + + + +``` + +```javascript +export default { + data() { + return { + // 数据源 + dynamicFormData: { + email: '', + domains: {} + }, + // 动态表单数据 + dynamicLists: [], + // 规则 + dynamicRules: { + email: { + rules: [{ + required: true, + errorMessage: '域名不能为空' + }, { + format: 'email', + errorMessage: '域名格式错误' + }] + } + } + } + }, + methods: { + // 新增表单域 + add() { + this.dynamicLists.push({ + label: '域名', + id: Date.now() + }) + }, + // 删除表单域 + del(id) { + let index = this.dynamicLists.findIndex(v => v.id === id) + this.dynamicLists.splice(index, 1) + }, + // 提交 + submit(ref) { + this.$refs[ref].validate((err,value)=>{ + console.log(err,value); + }) + }, + } +} + +// 返回值格式 ,根据自有业务,自行处理数据 +{ + emial:'', + domains:{ + id1:'', + id2:'', + ... + } +} +``` + + +### 表单校验时机说明 + +不管是在规则里还是`uni-forms`、`uni-forms-item`里,都有 `validateTrigger` 属性, `validateTrigger` 属性规定了表单校验时机,当前只有 `bind`、`submit` 两个值域 + +- `bind` : 数据绑定时触发校验,`uni-esayinput` 、`uni-data-checkbox` 组件表现为数据发生变化时。其他内置或三方组件为 `binddata` 事件执行时机 + +```html + + + +``` + +- `submit`: 只有提交表单才会触发表单校验 + + +对于表单校验时机,同时只会有一个 `validateTrigger` 发生作用,它的作用权重为 + +**`规则 > uni-forms-item > uni-forms `** + +- 如果规则里配置 `validateTrigger` ,则优先使用规则里的 `validateTrigger` 属性来决定表单校验时机 +- 如果规则里没有配置 `validateTrigger` ,则优先使用 `uni-forms-item` 的 `validateTrigger` 属性来决定表单校验时机 +- 如果 `uni-forms-item` 组件里没有配置 `validateTrigger` ,则优先使用 `uni-forms` 的 `validateTrigger` 属性来决定表单校验时机 +- 以此类推,如果都没有使用 `validateTrigger` 属性,则会使用 `uni-forms` 的 `validateTrigger` 属性默认值来决定表单校验时机 + + +## API + +### Forms Props + +|属性名|类型|默认值|可选值|说明| +|:-:|:-:|:-:|:-:|:-:| +|value [即将废弃]|Object|-|-| 表单数据| +|modelValue|Object|-|-| 表单数据| +|rules|Object|-|-|表单校验规则| +|validate-trigger|String|submit|bind/submit| 表单校验时机| +|label-position|String|left|top/left|label 位置| +|label-width|String/Number|75|-|label 宽度,单位 px| +|label-align|String|left| left/center/right|label 居中方式| +|err-show-type|String|undertext| undertext/toast/modal|表单错误信息提示方式| +|border|Boolean|false|-|是否显示分格线| + +### Forms Events + +|事件称名|说明| +|:-:|:-:| +|validate|任意表单项被校验后触发,返回表单校验信息| + +### Forms Methods + +|方法称名|说明| +|:-:| :-:| +|submit[即将废弃]| 对整个表单进行校验的方法,会返回一个 promise| +|validate|对整个表单进行校验的方法,会返回一个 promise| +|setValue|设置表单某一项 name 的对应值,通常在 uni-forms-item 和自定表单组件中使用| +|validateField|部分表单进行校验| +|clearValidate|移除表单的校验结果| +|resetFields|重置表单, 需要把 `uni-forms` 的`modelValue`属性改为 `v-model` ,且对内置组件可能不生效| + + + +### validate(keepItem:Array,callback:Function) 方法说明 +`validate` 方法是对整个表单进行校验,方法接受两个参数 + +|参数称名|类型|说明| +|:-:| :-:|:-:| +|keepItem|Array|保留不参与校验的字段| +|callback|Function|校验完成返回函数| + +校验成功后,校验对象只保留指定了`name`的字段(只要 ``uni-forms-item` 绑定了 `name`,哪怕不校验,也会返回),如果需要保留其他字段,则需要 `keepItem` 属性 + +```html + + + +``` + +```javascript +export default { + data() { + return { + formData:{ + age:'' + }, + rules: { + // ... + } + } + }, + onLoad(){ + this.formData.id = 'testId' + }, + methods: { + submit() { + // 在 onLoad 生命周期中,formData添加了一个 id 字段 ,此时这个字段是不参数校验的,所以结果中不返回 + // 在 validate(['id']) 方法中,指定第一个参数 ,即可返回id字段 + this.$refs.form.validate(['id'],(err,formData)=>{ + if(!err){ + console.log('success',formData) + } + }) + } + } +} + +``` + + +`validate` 方法还可以返回一个 `Promise` 对象,如果使用了 `callback` 则`Promise` 返回 `null`,`validate` 方法会优先使用 `callback`。 + +`callback` 方法会返回两个返回值 : +- 第一个返回值为检验结果,如果校验失败,则返回失败信息,如果成功,返回 `null` +- 第二个返回值校验数据 + + +```javascript + +// 使用 callback +// 如果不需要 keepItem 参数 ,则可以省略 +this.$refs.form.validate((err,formData)=>{ + // 如果校验成功 ,err 返回 null + if(!err){ + console.log('success',formData) + return + } + console.log('error',err) +}).then(res=>{ + // res 返回 null +}) + +// 使用 Promise +// 对整个表单进行校验,返回一个 Promise +this.$refs.form.validate().then((res)=>{ + // 成功返回,res 为表单数据 + // 其他逻辑处理 + // ... +}).catch((err)=>{ + // 表单校验验失败,err 为具体错误信息 + // 其他逻辑处理 + // ... +}) + +``` + +### setValue(name:String,value:any) 方法说明 + +`setValue` 方法通常用于内置组件或三方组件返回值的校验,因为`uni-esayinput` 等 uni 开头的组件内置了对 `uni-forms`的支持,所以这些组件返回的值可以直接使用,但是比如像`input` 这些内置组件值的变化,无法及时通知 `uni-forms` ,从而无法正常的校验,这时就需要我们手动将这些值加入到`uni-forms`的校验。 + +`setValue` 方法接受两个参数: +- name: 表单域对应的name +- value: 表单域对应的值 + +```html + + + +``` + +```javascript +export default { + data() { + return { + formData:{ + age:'' + }, + rules: { + // ... + } + } + }, + methods: { + setValue(name,value){ + // 设置表单某项对应得值来触发表单校验 + // 接受两个参数,第一个参数为表单域的 name ,第二个参数为表单域的值 + this.$refs.form.setValue(name,value) + }, + submit() { + this.$refs.form.validate(['id'],(err,formData)=>{ + if(!err){ + console.log('success',formData) + } + }) + } + } +} + +``` + +### 其他方法说明 + +```javascript + +// 部分表单进行校验,接受一个参数,类型为 String 或 Array ,只校验传入 name 表单域的值 +this.$refs.form.validateField(['name', 'email']).then((res)=>{ + // 成功返回,res 为对应表单数据 + // 其他逻辑处理 + // ... +}).catch((err)=>{ + // 表单校验验失败,err 为具体错误信息 + // 其他逻辑处理 + // ... +}) + +// 移除表单校验,接受一个参数,类型为 String 或 Array ,只移除传入 name 表单域的值,如果不传入参数,则移除所有 +this.$refs.form.clearValidate(['name', 'email']) + +``` + + +### FormsItem Props + +|属性名|类型|默认值|可选值 |说明| +|:-:|:-:|:-:|:-:|:-:| +|name|String|-|-|表单域的属性名,在使用校验规则时必填| +|required|Boolean|false|-|label 右边显示红色"*"号,样式显示不会对校验规则产生效果| +|validate-trigger|String|submit|bind/submit|表单校验时机| +|left-icon|String|-|-| label左边的图标,限uni-ui的图标名称| +|icon-color|String|#606266|-| 左边通过icon配置的图标的颜色| +|label|String|-|-| 输入框左边的文字提示| +|label-width|Number|70|-| label的宽度,单位px| +|label-align|String|left|left/center/right|label的文字对齐方式| +|label-position|String|left|top/left|label的文字的位置| +|error-message|String|-|-|显示的错误提示内容,如果为空字符串或者false,则不显示错误信息| + + +### FormsItem Slots +|插槽名|说明| +|:-:| :-:| +|default|默认插槽| +|left(已经失效,请使用label代替)|label插槽,自定义label显示内容| +|label|label插槽,自定义label显示内容| + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/forms/forms](https://hellouniapp.dcloud.net.cn/pages/extUI/forms/forms) \ No newline at end of file diff --git a/uni_modules/uni-goods-nav/changelog.md b/uni_modules/uni-goods-nav/changelog.md new file mode 100644 index 0000000..b0212bb --- /dev/null +++ b/uni_modules/uni-goods-nav/changelog.md @@ -0,0 +1,13 @@ +## 1.1.1(2021-08-24) +- 新增 支持国际化 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.5(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json b/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json new file mode 100644 index 0000000..dcdba41 --- /dev/null +++ b/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json @@ -0,0 +1,6 @@ +{ + "uni-goods-nav.options.shop": "shop", + "uni-goods-nav.options.cart": "cart", + "uni-goods-nav.buttonGroup.addToCart": "add to cart", + "uni-goods-nav.buttonGroup.buyNow": "buy now" +} diff --git a/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js b/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json b/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json new file mode 100644 index 0000000..48ee344 --- /dev/null +++ b/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "uni-goods-nav.options.shop": "店铺", + "uni-goods-nav.options.cart": "购物车", + "uni-goods-nav.buttonGroup.addToCart": "加入购物车", + "uni-goods-nav.buttonGroup.buyNow": "立即购买" +} diff --git a/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json b/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json new file mode 100644 index 0000000..d0a0255 --- /dev/null +++ b/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json @@ -0,0 +1,6 @@ +{ + "uni-goods-nav.options.shop": "店鋪", + "uni-goods-nav.options.cart": "購物車", + "uni-goods-nav.buttonGroup.addToCart": "加入購物車", + "uni-goods-nav.buttonGroup.buyNow": "立即購買" +} diff --git a/uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue b/uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue new file mode 100644 index 0000000..32ab1b9 --- /dev/null +++ b/uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/uni_modules/uni-goods-nav/package.json b/uni_modules/uni-goods-nav/package.json new file mode 100644 index 0000000..d7d56ee --- /dev/null +++ b/uni_modules/uni-goods-nav/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-goods-nav", + "displayName": "uni-goods-nav 商品导航", + "version": "1.1.1", + "description": "商品导航组件主要用于电商类应用底部导航,可自定义加入购物车,购买等操作", + "keywords": [ + "uni-ui", + "uniui", + "商品导航" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-goods-nav/readme.md b/uni_modules/uni-goods-nav/readme.md new file mode 100644 index 0000000..4e47bfd --- /dev/null +++ b/uni_modules/uni-goods-nav/readme.md @@ -0,0 +1,111 @@ + + +### GoodsNav 商品导航 +*已经支持在nvue页面中使用* + +商品加入购物车,立即购买,组件名:`uni-goods-nav`,代码块: uGoodsNav。 + +### 使用方式 + +引用组件 + +```javascript +import uniGoodsNav from '@/components/uni-goods-nav/uni-goods-nav.vue' +export default { + components: {uniGoodsNav} +} +``` + +使用组件 + +```html + +``` + +```javascript +export default { + data () { + return { + options: [{ + icon: 'headphones', + text: '客服' + }, { + icon: 'shop', + text: '店铺', + info: 2, + infoBackgroundColor:'#007aff', + infoColor:"red" + }, { + icon: 'cart', + text: '购物车', + info: 2 + }], + buttonGroup: [{ + text: '加入购物车', + backgroundColor: '#ff0000', + color: '#fff' + }, + { + text: '立即购买', + backgroundColor: '#ffa200', + color: '#fff' + } + ] + } + }, + methods: { + onClick (e) { + uni.showToast({ + title: `点击${e.content.text}`, + icon: 'none' + }) + }, + buttonClick (e) { + console.log(e) + this.options[2].info++ + } + } +} +``` + +### 属性说明 + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|options |Array |- |组件参数 | +|buttonGroup|Array |- |组件按钮组参数 | +|fill |Boolean|false |按钮是否平铺 | + + +**options 参数说明:** + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|text |String |- |显示文字 | +|icon |String | |图标,[参考](https://ext.dcloud.net.cn/plugin?id=28) | +|info |Number |0 |右上角数字角标 | +|infoBackgroundColor|String |#ff0000|角标背景色 | +|infoColor |String |#fff |角标前景色 | + +**buttonGroup 参数说明:** + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|text |String |- |按钮文字 | +|backgroundColor |String |- |按钮背景色 | +|color |String |- |字体颜色 | + +### 事件说明 + +|事件名 |说明 |返回值 | +|:-: |:-: |:-: | +|@click |左侧点击事件 |e = {index,content}| +|@buttonClick |右侧按钮组点击事件 |e = {index,content}| + +### 插件预览地址 + +[https://uniapp.dcloud.io/h5/pages/extUI/goods-nav/goods-nav](https://uniapp.dcloud.io/h5/pages/extUI/goods-nav/goods-nav) + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/goods-nav/goods-nav](https://hellouniapp.dcloud.net.cn/pages/extUI/goods-nav/goods-nav) \ No newline at end of file diff --git a/uni_modules/uni-grid/changelog.md b/uni_modules/uni-grid/changelog.md new file mode 100644 index 0000000..49fe008 --- /dev/null +++ b/uni_modules/uni-grid/changelog.md @@ -0,0 +1,8 @@ +## 1.3.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.3.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.2.4(2021-05-12) +- 新增 组件示例地址 +## 1.2.3(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue b/uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue new file mode 100644 index 0000000..1f2d79a --- /dev/null +++ b/uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/uni_modules/uni-grid/components/uni-grid/uni-grid.vue b/uni_modules/uni-grid/components/uni-grid/uni-grid.vue new file mode 100644 index 0000000..62bbc32 --- /dev/null +++ b/uni_modules/uni-grid/components/uni-grid/uni-grid.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/uni_modules/uni-grid/package.json b/uni_modules/uni-grid/package.json new file mode 100644 index 0000000..8b6da6a --- /dev/null +++ b/uni_modules/uni-grid/package.json @@ -0,0 +1,82 @@ +{ + "id": "uni-grid", + "displayName": "uni-grid 宫格", + "version": "1.3.1", + "description": "Grid 宫格组件,提供移动端常见的宫格布局,如九宫格。", + "keywords": [ + "uni-ui", + "uniui", + "九宫格", + "表格" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-grid/readme.md b/uni_modules/uni-grid/readme.md new file mode 100644 index 0000000..a748954 --- /dev/null +++ b/uni_modules/uni-grid/readme.md @@ -0,0 +1,95 @@ + + +## Grid 宫格 +> **组件名:uni-grid** +> 代码块: `uGrid` + + +宫格组件。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 删除组件自带圆点角标效果,完全交给用户实现,示例有简单角标效果实现 +> - Grid 组件仅在自定义组件模式下支持 +> - column 属性最大值最好不要超过 5 个,如果超过,需要注意内容显示 +> - 支付宝小程序平台需要在支付宝小程序开发者工具里开启 component2 编译模式,开启方式: `详情 --> 项目配置 --> 启用 component2 编译` +> - 为了避免高度显示错误组件内必须要有内容 + + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + + + 文本 + + + 文本 + + + 文本 + + + + + + + 文本 + + + 文本 + + + 文本 + + + 文本 + + + 文本 + + + 文本 + + +``` + +## API + +### Grid Props + +**uni-grid 属性说明:** + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|column |Number |3 |每列显示个数 | +|borderColor|String |#d0dee5|边框颜色 | +|showBorder |Boolean|true |是否显示边框 | +|square |Boolean|true |是否方形显示 | +|highlight |Boolean|true |点击背景是否高亮 | + +### Grid Events +|事件名 |说明 |返回值 | +|:-: |:-: |:-: | +|@change|点击 grid 触发 |e={detail:{index:0}},index 为当前点击 gird 下标| + + +### GridItem Props + +|属性名|类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|index|Number |- |子组件的唯一标识 ,点击gird会返回当前的标识| + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/grid/grid](https://hellouniapp.dcloud.net.cn/pages/extUI/grid/grid) \ No newline at end of file diff --git a/uni_modules/uni-group/changelog.md b/uni_modules/uni-group/changelog.md new file mode 100644 index 0000000..7348312 --- /dev/null +++ b/uni_modules/uni-group/changelog.md @@ -0,0 +1,8 @@ +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +- 优化 组件文档 +## 1.0.3(2021-05-12) +- 新增 组件示例地址 +## 1.0.2(2021-02-05) +- 调整为uni_modules目录规范 +- 优化 兼容 nvue 页面 diff --git a/uni_modules/uni-group/components/uni-group/uni-group.vue b/uni_modules/uni-group/components/uni-group/uni-group.vue new file mode 100644 index 0000000..25ddb29 --- /dev/null +++ b/uni_modules/uni-group/components/uni-group/uni-group.vue @@ -0,0 +1,130 @@ + + + + diff --git a/uni_modules/uni-group/package.json b/uni_modules/uni-group/package.json new file mode 100644 index 0000000..bdbaf95 --- /dev/null +++ b/uni_modules/uni-group/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-group", + "displayName": "uni-group 分组", + "version": "1.1.0", + "description": "分组组件可用于将组件用于分组,添加间隔,以产生明显的区块", + "keywords": [ + "uni-ui", + "uniui", + "group", + "分组", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-group/readme.md b/uni_modules/uni-group/readme.md new file mode 100644 index 0000000..deefb22 --- /dev/null +++ b/uni_modules/uni-group/readme.md @@ -0,0 +1,54 @@ + + +### Group 分组 + +分组组件可用于将组件分组,添加间隔,以产生明显的区块,组件名:``uni-group``,代码块: uGroup。 + +### 平台差异说明 + +如无特殊说明,则全平台可用 + +### 组件使用注意事项 + +为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 + +- 组件需要依赖 `sass` 插件 ,请自行手动安装 +- 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + + +### 使用方式 + +在 ``template`` 中使用组件 + +```html + + 分组1 的内容 + 分组1 的内容 + + + + 分组2 的内容 + 分组2 的内容 + +``` + +### 属性说明 + +|属性名|类型|默认值|说明| +|:-:|:-:|:-:|:-:| +|title|String|-|主标题| +|top|Number|-|分组间隔| +|mode|String|''|模式 ,card 为卡片模式| + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/group/group](https://hellouniapp.dcloud.net.cn/pages/extUI/group/group) \ No newline at end of file diff --git a/uni_modules/uni-icons/changelog.md b/uni_modules/uni-icons/changelog.md new file mode 100644 index 0000000..4cde217 --- /dev/null +++ b/uni_modules/uni-icons/changelog.md @@ -0,0 +1,8 @@ +## 1.2.1(2021-09-17) +- 新增 支持使用 css 图标库扩展组件(仅 vue 支持) +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.5(2021-05-12) +- 新增 组件示例地址 +## 1.1.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-icons/components/uni-icons/icons.js b/uni_modules/uni-icons/components/uni-icons/icons.js new file mode 100644 index 0000000..60b7332 --- /dev/null +++ b/uni_modules/uni-icons/components/uni-icons/icons.js @@ -0,0 +1,132 @@ +export default { + "pulldown": "\ue588", + "refreshempty": "\ue461", + "back": "\ue471", + "forward": "\ue470", + "more": "\ue507", + "more-filled": "\ue537", + "scan": "\ue612", + "qq": "\ue264", + "weibo": "\ue260", + "weixin": "\ue261", + "pengyouquan": "\ue262", + "loop": "\ue565", + "refresh": "\ue407", + "refresh-filled": "\ue437", + "arrowthindown": "\ue585", + "arrowthinleft": "\ue586", + "arrowthinright": "\ue587", + "arrowthinup": "\ue584", + "undo-filled": "\ue7d6", + "undo": "\ue406", + "redo": "\ue405", + "redo-filled": "\ue7d9", + "bars": "\ue563", + "chatboxes": "\ue203", + "camera": "\ue301", + "chatboxes-filled": "\ue233", + "camera-filled": "\ue7ef", + "cart-filled": "\ue7f4", + "cart": "\ue7f5", + "checkbox-filled": "\ue442", + "checkbox": "\ue7fa", + "arrowleft": "\ue582", + "arrowdown": "\ue581", + "arrowright": "\ue583", + "smallcircle-filled": "\ue801", + "arrowup": "\ue580", + "circle": "\ue411", + "eye-filled": "\ue568", + "eye-slash-filled": "\ue822", + "eye-slash": "\ue823", + "eye": "\ue824", + "flag-filled": "\ue825", + "flag": "\ue508", + "gear-filled": "\ue532", + "reload": "\ue462", + "gear": "\ue502", + "hand-thumbsdown-filled": "\ue83b", + "hand-thumbsdown": "\ue83c", + "hand-thumbsup-filled": "\ue83d", + "heart-filled": "\ue83e", + "hand-thumbsup": "\ue83f", + "heart": "\ue840", + "home": "\ue500", + "info": "\ue504", + "home-filled": "\ue530", + "info-filled": "\ue534", + "circle-filled": "\ue441", + "chat-filled": "\ue847", + "chat": "\ue263", + "mail-open-filled": "\ue84d", + "email-filled": "\ue231", + "mail-open": "\ue84e", + "email": "\ue201", + "checkmarkempty": "\ue472", + "list": "\ue562", + "locked-filled": "\ue856", + "locked": "\ue506", + "map-filled": "\ue85c", + "map-pin": "\ue85e", + "map-pin-ellipse": "\ue864", + "map": "\ue364", + "minus-filled": "\ue440", + "mic-filled": "\ue332", + "minus": "\ue410", + "micoff": "\ue360", + "mic": "\ue302", + "clear": "\ue434", + "smallcircle": "\ue868", + "close": "\ue404", + "closeempty": "\ue460", + "paperclip": "\ue567", + "paperplane": "\ue503", + "paperplane-filled": "\ue86e", + "person-filled": "\ue131", + "contact-filled": "\ue130", + "person": "\ue101", + "contact": "\ue100", + "images-filled": "\ue87a", + "phone": "\ue200", + "images": "\ue87b", + "image": "\ue363", + "image-filled": "\ue877", + "location-filled": "\ue333", + "location": "\ue303", + "plus-filled": "\ue439", + "plus": "\ue409", + "plusempty": "\ue468", + "help-filled": "\ue535", + "help": "\ue505", + "navigate-filled": "\ue884", + "navigate": "\ue501", + "mic-slash-filled": "\ue892", + "search": "\ue466", + "settings": "\ue560", + "sound": "\ue590", + "sound-filled": "\ue8a1", + "spinner-cycle": "\ue465", + "download-filled": "\ue8a4", + "personadd-filled": "\ue132", + "videocam-filled": "\ue8af", + "personadd": "\ue102", + "upload": "\ue402", + "upload-filled": "\ue8b1", + "starhalf": "\ue463", + "star-filled": "\ue438", + "star": "\ue408", + "trash": "\ue401", + "phone-filled": "\ue230", + "compose": "\ue400", + "videocam": "\ue300", + "trash-filled": "\ue8dc", + "download": "\ue403", + "chatbubble-filled": "\ue232", + "chatbubble": "\ue202", + "cloud-download": "\ue8e4", + "cloud-upload-filled": "\ue8e5", + "cloud-upload": "\ue8e6", + "cloud-download-filled": "\ue8e9", + "headphones":"\ue8bf", + "shop":"\ue609" +} diff --git a/uni_modules/uni-icons/components/uni-icons/uni-icons.vue b/uni_modules/uni-icons/components/uni-icons/uni-icons.vue new file mode 100644 index 0000000..6bcc0a2 --- /dev/null +++ b/uni_modules/uni-icons/components/uni-icons/uni-icons.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/uni_modules/uni-icons/components/uni-icons/uni.ttf b/uni_modules/uni-icons/components/uni-icons/uni.ttf new file mode 100644 index 0000000000000000000000000000000000000000..60a1968d08cc6056c70b5402b2effac43c6f96a3 GIT binary patch literal 26164 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 literal 0 HcmV?d00001 diff --git a/uni_modules/uni-icons/package.json b/uni_modules/uni-icons/package.json new file mode 100644 index 0000000..2dd573c --- /dev/null +++ b/uni_modules/uni-icons/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-icons", + "displayName": "uni-icons 图标", + "version": "1.2.1", + "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。", + "keywords": [ + "uni-ui", + "uniui", + "icon", + "图标" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-icons/readme.md b/uni_modules/uni-icons/readme.md new file mode 100644 index 0000000..efc0a97 --- /dev/null +++ b/uni_modules/uni-icons/readme.md @@ -0,0 +1,64 @@ + + +## Icons 图标 +> **组件名:uni-icons** +> 代码块: `uIcons` + + +用于展示 icons 图标 。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + +``` + +### 扩展图标用法 + +1. 需要自行在项目 App.vue 中引入 css 图标扩展库(注意: css 图标库引用的 .ttf 文件路径是否正确) + + ```html + + ``` +2. 在 ``template`` 中使用组件 + + ```html + + ``` + + + + +## API + +### Icons Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|size |Number |24 |图标大小 | +|type |String |- |图标图案,参考示例 | +|color |String |- |图标颜色 | +|font-family(仅 vue 支持) |String |uniicons |图标库字体家族 | + + +### Icons Events +|事件名 |说明 |返回值| +|:-: |:-: |:-: | +|@click|点击 Icon 触发事件|- | + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/icons/icons](https://hellouniapp.dcloud.net.cn/pages/extUI/icons/icons) \ No newline at end of file diff --git a/uni_modules/uni-id-cf/changelog.md b/uni_modules/uni-id-cf/changelog.md new file mode 100644 index 0000000..0b22d64 --- /dev/null +++ b/uni_modules/uni-id-cf/changelog.md @@ -0,0 +1,19 @@ +## 1.0.7(2021-10-20) +新增bindMobileByMpWeixin,一键获取微信绑定的手机号 +## 1.0.6(2021-09-23) +修复微信登录成功后没有添加日志的问题 +## 1.0.5(2021-08-10) +- 修复登录成功后响应体包含userInfo.password的问题 +- 新增微信登录成功后,自动获取用户的微信昵称和头像完善用户个人资料 +## 1.0.4(2021-07-31) +- 修复 登录日志在登录失败时不写入记录的 bug +- 修复 写入记录登录是未传递 type 参数的 bug +## 1.0.3(2021-07-02) +- 框架设定非 admin 不能创建用户, 用户可自定义 +## 1.0.2(2021-07-01) +1. 发送短信验证码api,默认注释掉:虚拟发送短信验证码的代码块。 +2. 统一action名称为驼峰法 +## 1.0.1(2021-06-28) +修复resetPwdBySmsCode接口,未注册过的用户也能调用的问题 +## 1.0.0(2021-06-21) +1.0.0版发布 \ No newline at end of file diff --git a/uni_modules/uni-id-cf/package.json b/uni_modules/uni-id-cf/package.json new file mode 100644 index 0000000..f376744 --- /dev/null +++ b/uni_modules/uni-id-cf/package.json @@ -0,0 +1,81 @@ +{ + "id": "uni-id-cf", + "displayName": "uni-id-cf", + "version": "1.0.7", + "description": "uni-id-cf", + "keywords": [ + "uni-id-cf", + "uni-id的云函数" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "uniCloud", + "云函数模板" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": ["uni-config-center","uni-captcha","uni-id"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "u", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "u", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "u", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-id-cf/readme.md b/uni_modules/uni-id-cf/readme.md new file mode 100644 index 0000000..75be0bb --- /dev/null +++ b/uni_modules/uni-id-cf/readme.md @@ -0,0 +1,7 @@ +#### uni-id-cf是uni-id-uniCloudFunction的缩写。 + +#### 直接调用他内置的云函数,即可直接使用uni-id的各类api。 + +含:登录注册(含用户名密码登录、手机号验证码登录、app一键登录、微信登录、Apple登录、微信小程序登录)、修改密码、忘记密码、退出登录等 + +> 详细的使用方式见[uni-starter](https://ext.dcloud.net.cn/plugin?id=5057) \ No newline at end of file diff --git a/uni_modules/uni-id-cf/uniCloud/cloudfunctions/uni-id-cf/index.js b/uni_modules/uni-id-cf/uniCloud/cloudfunctions/uni-id-cf/index.js new file mode 100644 index 0000000..be48761 --- /dev/null +++ b/uni_modules/uni-id-cf/uniCloud/cloudfunctions/uni-id-cf/index.js @@ -0,0 +1,580 @@ +'use strict'; +let uniID = require('uni-id') +const uniCaptcha = require('uni-captcha') +const createConfig = require('uni-config-center') +const uniIdConfig = createConfig({ + pluginId: 'uni-id' +}).config() +const db = uniCloud.database() +const dbCmd = db.command +const usersDB = db.collection('uni-id-users') +exports.main = async (event, context) => { + //UNI_WYQ:这里的uniID换成新的,保证多人访问不会冲突 + uniID = uniID.createInstance({ + context + }) + console.log('event : ' + JSON.stringify(event)) + /* + 1.event为客户端 uniCloud.callFunction填写的data的值,这里介绍一下其中的属性 + action:表示要执行的任务名称、比如:登录login、退出登录 logout等 + params:业务数据内容 + uniIdToken:系统自动传递的token,数据来源客户端的 uni.getStorageSync('uni_id_token') + */ + const { + action, + uniIdToken, + inviteCode + } = event; + const deviceInfo = event.deviceInfo || {}; + let params = event.params || {}; + /* + 2.在某些操作之前我们要对用户对身份进行校验(也就是要检查用户的token)再将得到的uid写入params.uid + 校验用到的方法是uniID.checkToken 详情:https://uniapp.dcloud.io/uniCloud/uni-id?id=checktoken + + 讨论,我们假设一个这样的场景,代码如下。 + 如: + uniCloud.callFunction({ + name:"xxx", + data:{ + "params":{ + uid:"通过某种方式获取来的别人的uid" + } + } + }) + 用户就这样轻易地伪造了他人的uid传递给服务端,有一句话叫:前端传来的数据都是不可信任的 + 所以这里我们需要将uniID.checkToken返回的uid写入到params.uid + */ + let noCheckAction = ['register', 'checkToken', 'login', 'logout', 'sendSmsCode', 'createCaptcha', + 'verifyCaptcha', 'refreshCaptcha', 'inviteLogin', 'loginByWeixin', 'loginByUniverify', + 'loginByApple', 'loginBySms', 'resetPwdBySmsCode', 'registerAdmin' + ] + if (!noCheckAction.includes(action)) { + if (!uniIdToken) { + return { + code: 403, + msg: '缺少token' + } + } + let payload = await uniID.checkToken(uniIdToken) + if (payload.code && payload.code > 0) { + return payload + } + params.uid = payload.uid + } + + //禁止前台用户传递角色 + if (action.slice(0, 7) == "loginBy") { + if (params.role) { + return { + code: 403, + msg: '禁止前台用户传递角色' + } + } + } + + // 3.注册成功后触发。 + async function registerSuccess(uid) { + //用户接受邀请 + if (inviteCode) { + await uniID.acceptInvite({ + inviteCode, + uid + }); + } + //添加当前用户设备信息 + await db.collection('uni-id-device').add({ + ...deviceInfo, + user_id: uid + }) + } + //4.记录成功登录的日志方法 + const loginLog = async (res = {}) => { + const now = Date.now() + const uniIdLogCollection = db.collection('uni-id-log') + let logData = { + deviceId: params.deviceId || context.DEVICEID, + ip: params.ip || context.CLIENTIP, + type: res.type, + ua: context.CLIENTUA, + create_date: now + }; + + if(res.code === 0){ + logData.user_id = res.uid + logData.state = 1 + if(res.userInfo&&res.userInfo.password){ + delete res.userInfo.password + } + if (res.type == 'register') { + await registerSuccess(res.uid) + } else { + if (Object.keys(deviceInfo).length) { + // console.log(979797, { + // deviceInfo, + // user_id: res + // }); + //更新当前用户设备信息 + await db.collection('uni-id-device').where({ + user_id: res.uid + }).update(deviceInfo) + } + } + }else{ + logData.state = 0 + } + return await uniIdLogCollection.add(logData) + } + + let res = {} + switch (action) { //根据action的值执行对应的操作 + case 'refreshSessionKey': + let getSessionKey = await uniID.code2SessionWeixin({code:params.code}); + if(getSessionKey.code){ + return getSessionKey + } + res = await uniID.updateUser({ + uid: params.uid, + sessionKey:getSessionKey.sessionKey + }) + console.log(res); + break; + case 'bindMobileByMpWeixin': + console.log(params); + let getSessionKeyRes = await uniID.getUserInfo({ + uid: params.uid, + field: ['sessionKey'] + }) + if(getSessionKeyRes.code){ + return getSessionKeyRes + } + let sessionKey = getSessionKeyRes.userInfo.sessionKey + console.log(getSessionKeyRes); + res = await uniID.wxBizDataCrypt({ + ...params, + sessionKey + }) + console.log(res); + if(res.code){ + return res + } + res = await uniID.bindMobile({ + uid: params.uid, + mobile: res.purePhoneNumber + }) + console.log(res); + break; + case 'bindMobileByUniverify': + let { + appid, apiKey, apiSecret + } = uniIdConfig.service.univerify + let univerifyRes = await uniCloud.getPhoneNumber({ + provider: 'univerify', + appid, + apiKey, + apiSecret, + access_token: params.access_token, + openid: params.openid + }) + if (univerifyRes.code === 0) { + res = await uniID.bindMobile({ + uid: params.uid, + mobile: univerifyRes.phoneNumber + }) + res.mobile = univerifyRes.phoneNumber + } + break; + case 'bindMobileBySms': + // console.log({ + // uid: params.uid, + // mobile: params.mobile, + // code: params.code + // }); + res = await uniID.bindMobile({ + uid: params.uid, + mobile: params.mobile, + code: params.code + }) + // console.log(res); + break; + case 'register': + var { + username, password, nickname + } = params + if (/^1\d{10}$/.test(username)) { + return { + code: 401, + msg: '用户名不能是手机号' + } + }; + if (/^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/.test(username)) { + return { + code: 401, + msg: '用户名不能是邮箱' + } + } + res = await uniID.register({ + username, + password, + nickname, + inviteCode + }); + if (res.code === 0) { + await registerSuccess(res.uid) + } + break; + case 'login': + //防止黑客恶意破解登录,连续登录失败一定次数后,需要用户提供验证码 + const getNeedCaptcha = async () => { + //当用户最近“2小时内(recordDate)”登录失败达到2次(recordSize)时。要求用户提交验证码 + const now = Date.now(), + recordDate = 120 * 60 * 1000, + recordSize = 2; + const uniIdLogCollection = db.collection('uni-id-log') + let recentRecord = await uniIdLogCollection.where({ + deviceId: params.deviceId || context.DEVICEID, + create_date: dbCmd.gt(now - recordDate), + type: 'login' + }) + .orderBy('create_date', 'desc') + .limit(recordSize) + .get(); + return recentRecord.data.filter(item => item.state === 0).length === recordSize; + } + + let passed = false; + let needCaptcha = await getNeedCaptcha(); + console.log('needCaptcha', needCaptcha); + if (needCaptcha) { + res = await uniCaptcha.verify({ + ...params, + scene: 'login' + }) + if (res.code === 0) passed = true; + } + + if (!needCaptcha || passed) { + res = await uniID.login({ + ...params, + queryField: ['username', 'email', 'mobile'] + }); + res.type = 'login' + await loginLog(res); + needCaptcha = await getNeedCaptcha(); + } + + res.needCaptcha = needCaptcha; + break; + case 'loginByWeixin': + let loginRes = await uniID.loginByWeixin(params); + if(loginRes.code===0){ + //用户完善资料(昵称、头像) + if(context.PLATFORM == "app-plus" && !loginRes.userInfo.nickname){ + let {accessToken:access_token,openid} = loginRes, + {appid,appsecret:secret} = uniIdConfig['app-plus'].oauth.weixin; + let wxRes = await uniCloud.httpclient.request( + `https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&scope=snsapi_userinfo&appid=${appid}&secret=${secret}`, { + method: 'POST', + contentType: 'json', // 指定以application/json发送data内的数据 + dataType: 'json' // 指定返回值为json格式,自动进行parse + }) + if(wxRes.status == 200){ + let {nickname,headimgurl} = wxRes.data; + let headimgurlFile = {},cloudPath = loginRes.uid+'/'+Date.now()+"headimgurl.jpg"; + let getImgBuffer = await uniCloud.httpclient.request(headimgurl) + if(getImgBuffer.status == 200){ + let {fileID} = await uniCloud.uploadFile({ + cloudPath, + fileContent: getImgBuffer.data + }); + headimgurlFile = { + name:cloudPath, + extname:"jpg", + url:fileID + } + }else{ + return getImgBuffer + } + await uniID.updateUser({ + uid: loginRes.uid, + nickname, + avatar_file:headimgurlFile + }) + loginRes.userInfo.nickname = nickname; + loginRes.userInfo.avatar_file = headimgurlFile; + }else{ + return wxRes + } + } + if(context.PLATFORM == "mp-weixin"){ + let resUpdateUser = await uniID.updateUser({ + uid: loginRes.uid, + sessionKey:loginRes.sessionKey + }) + console.log(resUpdateUser); + } + delete loginRes.openid + delete loginRes.sessionKey + delete loginRes.accessToken + delete loginRes.refreshToken + } + await loginLog(loginRes) + return loginRes + break; + case 'loginByUniverify': + res = await uniID.loginByUniverify(params) + await loginLog(res) + break; + case 'loginByApple': + res = await uniID.loginByApple(params) + await loginLog(res) + break; + case 'checkToken': + res = await uniID.checkToken(uniIdToken); + break; + case 'logout': + res = await uniID.logout(uniIdToken) + break; + case 'sendSmsCode': + /* -开始- 测试期间,为节约资源。统一虚拟短信验证码为: 123456;开启以下代码块即可 */ + return uniID.setVerifyCode({ + mobile: params.mobile, + code: '123456', + type: params.type + }) + /* -结束- */ + + // 简单限制一下客户端调用频率 + const ipLimit = await db.collection('opendb-verify-codes').where({ + ip: context.CLIENTIP, + created_at: dbCmd.gt(Date.now() - 60000) + }).get() + if (ipLimit.data.length > 0) { + return { + code: 429, + msg: '请求过于频繁' + } + } + const templateId = '11753' // 替换为自己申请的模板id + if (!templateId) { + return { + code: 500, + msg: 'sendSmsCode需要传入自己的templateId,参考https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=sendsmscode' + } + } + const randomStr = '00000' + Math.floor(Math.random() * 1000000) + const code = randomStr.substring(randomStr.length - 6) + res = await uniID.sendSmsCode({ + mobile: params.mobile, + code, + type: params.type, + templateId + }) + break; + case 'loginBySms': + if (!params.code) { + return { + code: 500, + msg: '请填写验证码' + } + } + if (!/^1\d{10}$/.test(params.mobile)) { + return { + code: 500, + msg: '手机号码填写错误' + } + } + res = await uniID.loginBySms(params) + await loginLog(res) + break; + case 'resetPwdBySmsCode': + if (!params.code) { + return { + code: 500, + msg: '请填写验证码' + } + } + if (!/^1\d{10}$/.test(params.mobile)) { + return { + code: 500, + msg: '手机号码填写错误' + } + } + params.type = 'login' + let loginBySmsRes = await uniID.loginBySms(params) + // console.log(loginBySmsRes); + if (loginBySmsRes.code === 0) { + res = await uniID.resetPwd({ + password: params.password, + "uid": loginBySmsRes.uid + }) + } else { + return loginBySmsRes + } + break; + case 'getInviteCode': + res = await uniID.getUserInfo({ + uid: params.uid, + field: ['my_invite_code'] + }) + if (res.code === 0) { + res.myInviteCode = res.userInfo.my_invite_code + delete res.userInfo + } + break; + case 'getInvitedUser': + res = await uniID.getInvitedUser(params) + break; + case 'updatePwd': + res = await uniID.updatePwd(params) + break; + case 'createCaptcha': + res = await uniCaptcha.create(params) + break; + case 'refreshCaptcha': + res = await uniCaptcha.refresh(params) + break; + case 'getUserInviteCode': + res = await uniID.getUserInfo({ + uid: params.uid, + field: ['my_invite_code'] + }) + if (!res.userInfo.my_invite_code) { + res = await uniID.setUserInviteCode({ + uid: params.uid + }) + } + break; + + // =========================== admin api start ========================= + case 'registerAdmin': { + var { + username, + password + } = params + let { + total + } = await db.collection('uni-id-users').where({ + role: 'admin' + }).count() + if (total) { + return { + code: 10001, + message: '超级管理员已存在,请登录...' + } + } + const appid = params.appid + const appName = params.appName + delete params.appid + delete params.appName + res = await uniID.register({ + username, + password, + role: ["admin"] + }) + if (res.code === 0) { + const app = await db.collection('opendb-app-list').where({ + appid + }).count() + if (!app.total) { + await db.collection('opendb-app-list').add({ + appid, + name: appName, + description: "admin 管理后台", + create_date: Date.now() + }) + } + + } + } + break; + case 'registerUser': + const { + userInfo + } = await uniID.getUserInfo({ + uid: params.uid + }) + if (userInfo.role.indexOf('admin') === -1) { + res = { + code: 403, + message: '非法访问, 无权限注册超级管理员', + } + } else { + // 过滤 dcloud_appid,注册用户成功后再提交 + const dcloudAppidList = params.dcloud_appid + delete params.dcloud_appid + res = await uniID.register({ + autoSetDcloudAppid: false, + ...params + }) + if (res.code === 0) { + delete res.token + delete res.tokenExpired + await uniID.setAuthorizedAppLogin({ + uid: res.uid, + dcloudAppidList + }) + } + } + break; + case 'updateUser': { + const { + userInfo + } = await uniID.getUserInfo({ + uid: params.uid + }) + if (userInfo.role.indexOf('admin') === -1) { + res = { + code: 403, + message: '非法访问, 无权限注册超级管理员', + } + } else { + // 过滤 dcloud_appid,注册用户成功后再提交 + const dcloudAppidList = params.dcloud_appid + delete params.dcloud_appid + + // 过滤 password,注册用户成功后再提交 + const password = params.password + delete params.password + + // 过滤 uid、id + const id = params.id + delete params.id + delete params.uid + + + res = await uniID.updateUser({ + uid: id, + ...params + }) + if (res.code === 0) { + if (password) { + await uniID.resetPwd({ + uid: id, + password + }) + } + await uniID.setAuthorizedAppLogin({ + uid: id, + dcloudAppidList + }) + } + } + break; + } + case 'getCurrentUserInfo': + res = await uniID.getUserInfo({ + uid: params.uid, + ...params + }) + break; + // =========================== admin api end ========================= + default: + res = { + code: 403, + msg: '非法访问' + } + break; + } + //返回数据给客户端 + return res +} diff --git a/uni_modules/uni-id-cf/uniCloud/cloudfunctions/uni-id-cf/package.json b/uni_modules/uni-id-cf/uniCloud/cloudfunctions/uni-id-cf/package.json new file mode 100644 index 0000000..e6ae55c --- /dev/null +++ b/uni_modules/uni-id-cf/uniCloud/cloudfunctions/uni-id-cf/package.json @@ -0,0 +1,16 @@ +{ + "name": "uni-id-cf", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "uni-captcha": "file:../../../../uni-captcha/uniCloud/cloudfunctions/common/uni-captcha", + "uni-config-center": "file:../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center", + "uni-id": "file:../../../../uni-id/uniCloud/cloudfunctions/common/uni-id" + } +} diff --git a/uni_modules/uni-id/changelog.md b/uni_modules/uni-id/changelog.md new file mode 100644 index 0000000..9615838 --- /dev/null +++ b/uni_modules/uni-id/changelog.md @@ -0,0 +1,54 @@ +## 3.3.6(2021-09-08) +- 修复 邀请码可能重复的Bug +## 3.3.5(2021-08-10) +- 修复版本号错误 +## 3.3.4(2021-08-10) +- 微信、QQ、支付宝登录新增type参数用于指定当前是登录还是注册 +## 3.3.3(2021-08-04) +- 修复使用数组形式的配置文件报错的Bug +## 3.3.2(2021-08-03) +- 修复上3.3.0版本引出的createInstance接口传入配置不生效的Bug 感谢[hmh](https://gitee.com/hmh) +## 3.3.1(2021-07-30) +- 修复 将设置用户允许登录的应用列表时传入空数组报错的Bug +## 3.3.0(2021-07-30) +- 新增 不同端应用配置隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-config) +- 新增 不同端用户隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-user) + + 此版本升级需要开发者处理一下用户数据,请参考 [补齐用户dcloud_appid字段](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=makeup-dcloud-appid) +- 新增 QQ登录、注册相关功能 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=qq) +- 调整 不再支持绑定手机、邮箱时不填验证码直接绑定 +## 3.2.1(2021-07-09) +- 撤销3.2.0版本所做的调整 +## 3.2.0(2021-07-09) +- 【重要】支持不同端(管理端、用户端等)用户隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-user) +- 支持不同端(管理端、用户端等)配置文件隔离 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=isolate-config) +## 3.1.3(2021-07-08) +- 移除插件内误传的node_modules +## 3.1.2(2021-07-08) +- 修复 微信小程序绑定微信账号时报错的Bug +## 3.1.1(2021-07-01) +- 使用新的错误码规范,兼容旧版 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=errcode) +- 修复微信登录、绑定时未返回用户accessToken的Bug +## 3.1.0(2021-04-19) +- 增加对用户名、邮箱、密码字段的两端去空格 +- 默认忽略用户名、邮箱的大小写 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=case-sensitive) +- 修复 customToken导出async方法报错的Bug +## 3.0.12(2021-04-13) +- 调整bindTokenToDevice默认值为false +## 3.0.11(2021-04-12) +- 修复3.0.7版本引出的多个用户访问时可能出现30201报错的Bug +## 3.0.10(2021-04-08) +- 优化错误提示 +## 3.0.9(2021-04-08) +- bindMobile接口支持通过一键登录的方式绑定 +- 优化错误提示 +## 3.0.8(2021-03-19) +- 修复 3.0.7版本某些情况下生成token报错的Bug +## 3.0.7(2021-03-19) +- 新增 支持uni-config-center,更新uni-id无须再担心配置被覆盖 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=uni-config-center) +- 新增 自定义token内容,可以缓存角色权限之外的更多信息到客户端 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=custom-token) +- 新增 支持传入context获取uni-id实例,防止单实例多并发时全局context混乱 [详情](https://uniapp.dcloud.io/uniCloud/uni-id?id=create-instance) +## 3.0.6(2021-03-05) +- 新增[uniID.wxBizDataCrypt](https://uniapp.dcloud.io/uniCloud/uni-id?id=%e5%be%ae%e4%bf%a1%e6%95%b0%e6%8d%ae%e8%a7%a3%e5%af%86)方法 +- 优化loginByApple方法,提高接口响应速度 +## 3.0.5(2021-02-03) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-id/package.json b/uni_modules/uni-id/package.json new file mode 100644 index 0000000..f94bee5 --- /dev/null +++ b/uni_modules/uni-id/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-id", + "displayName": "uni-id", + "version": "3.3.6", + "description": "简单、统一、可扩展的用户中心", + "keywords": [ + "uniid", + "uni-id", + "用户管理", + "用户中心", + "短信验证码" +], + "repository": "https://gitee.com/dcloud/uni-id.git", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "uniCloud", + "云函数模板" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": ["uni-config-center"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "u", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "u", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "u", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-id/readme.md b/uni_modules/uni-id/readme.md new file mode 100644 index 0000000..ea7751c --- /dev/null +++ b/uni_modules/uni-id/readme.md @@ -0,0 +1,33 @@ +**文档已移至[uni-id文档](https://uniapp.dcloud.net.cn/uniCloud/uni-id)** + +> 一般uni-id升级大版本时为不兼容更新,从低版本迁移到高版本请参考:[uni-id迁移指南](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=migration) + +## 重要升级说明 + +**uni-id 3.x版本,搭配的uniCloud admin版本需大于1.2.10。** + +### 缓存角色权限 + +自`uni-id 3.0.0`起,支持在token内缓存用户的角色权限,默认开启此功能,各登录接口的needPermission参数不再生效。如需关闭请在config内配置`"removePermissionAndRoleFromToken": true`。 + +为什么要缓存角色权限?要知道云数据库是按照读写次数来收取费用的,并且读写数据库会拖慢接口响应速度。未配置`"removePermissionAndRoleFromToken": true`的情况下,可以在调用checkToken接口时不查询数据库获取用户角色权限。 + +详细checkToken流程如下: + +![](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/ed45d350-5a4d-11eb-b997-9918a5dda011.jpg) + +可以看出,旧版token(removePermissionAndRoleFromToken为true时生成的)在checkToken时如需返回权限需要进行两次数据库查询。新版token不需要查库即可返回权限信息。 + +**注意** + +- 由于角色权限缓存在token内,可能会存在权限已经更新但是用户token未过期之前依然是旧版角色权限的情况。可以调短一些token过期时间来减少这种情况的影响。 +- admin角色token内不包含permission,如需自行判断用户是否有某个权限,要注意admin角色需要额外判断一下,写法如下 + ```js + const { + role, + permission + } = await uniID.checkToken(event.uniIdToken) + if(role.includes('admin') || permission.includes('your permission id')) { + // 当前角色拥有'your permission id'对应的权限 + } + ``` \ No newline at end of file diff --git a/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/LICENSE.md b/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/LICENSE.md new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/LICENSE.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/index.js b/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/index.js new file mode 100644 index 0000000..2888a91 --- /dev/null +++ b/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/index.js @@ -0,0 +1 @@ +"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}var t=e(require("fs")),r=e(require("path")),n=e(require("crypto")),i=e(require("querystring")),o=e(require("buffer")),s=e(require("stream")),a=e(require("util"));const c=Object.prototype.toString,u=Object.prototype.hasOwnProperty;var d=/[\\^$.*+?()[\]{}|]/g,l=RegExp(d.source);function p(e,t,r){return e.replace(new RegExp((n=t)&&l.test(n)?n.replace(d,"\\$&"):n,"g"),r);var n}function f(e,t){return u.call(e,t)}function m(e){return"[object Object]"===c.call(e)}function h(e){return"function"==typeof e}function g(e){return!!e&&("object"==typeof e||"function"==typeof e)&&"function"==typeof e.then}function y(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}const w=/_(\w)/g,_=/[A-Z]/g;function v(e){return e.replace(w,(e,t)=>t?t.toUpperCase():"")}function b(e){return e.replace(_,e=>"_"+e.toLowerCase())}function E(e,t){let r,n;switch(t){case"snake2camel":n=v,r=w;break;case"camel2snake":n=b,r=_}for(const i in e)if(f(e,i)&&r.test(i)){const r=n(i);e[r]=e[i],delete e[i],m(e[r])?e[r]=E(e[r],t):Array.isArray(e[r])&&(e[r]=e[r].map(e=>E(e,t)))}return e}function C(e){return E(e,"snake2camel")}function T(e){return E(e,"camel2snake")}function A(e){return function(e,t="-"){e=e||new Date;const r=[];return r.push(e.getFullYear()),r.push(("00"+(e.getMonth()+1)).substr(-2)),r.push(("00"+e.getDate()).substr(-2)),r.join(t)}(e=e||new Date)+" "+function(e,t=":"){e=e||new Date;const r=[];return r.push(("00"+e.getHours()).substr(-2)),r.push(("00"+e.getMinutes()).substr(-2)),r.push(("00"+e.getSeconds()).substr(-2)),r.join(t)}(e)}function S(){"development"===process.env.NODE_ENV&&console.log(...arguments)}function I(e=6){let t="";for(let r=0;r0:t=d,r="年";break;case u>0:t=u,r="月";break;case c>0:t=c,r="天";break;case a>0:t=a,r="小时";break;case s>0:t=s,r="分钟";break;default:t=o,r="秒"}return`${t}${r}${i}`}function U(e){if(m(e))if(0===e.code)e.errCode=e.code,e.message=e.errMsg=e.msg,delete e.messageValues;else if(f(R,e.code)){const t=R[e.code];e.errCode="uni-id-"+t.errCode,e.errDetail=`${e.code}, ${e.msg}`,e.errMsg=function(e,t={}){const r=R[e];if(!r)throw new Error("unsupported error code: "+e);let n=r.errMsg;return Object.keys(t).forEach(e=>{n=p(n,`{${e}}`,t[e])}),n}(e.code,e.messageValues||{})||e.msg,e.message=e.msg=e.errMsg,delete e.messageValues}else e.code&&console.warn(`error code not found, error code: ${e.code}, please contact us`)}const V=uniCloud.database(),M=V.collection("uni-id-users"),L=V.collection("opendb-verify-codes"),B=V.collection("uni-id-roles"),q=V.collection("uni-id-permissions"),$={username:"用户名",mobile:"手机号",email:"邮箱",wx_unionid:"微信账号","wx_openid.app-plus":"微信账号","wx_openid.mp-weixin":"微信账号",qq_unionid:"QQ账号","qq_openid.app-plus":"QQ账号","qq_openid.mp-weixin":"QQ账号",ali_openid:"支付宝账号",apple_openid:"苹果账号"},F=90002,K=90003,H=90004,G=90005;async function Q({name:e,url:t,data:r,options:n,defaultOptions:i}){let o={};const s=T(Object.assign({},r));s&&s.access_token&&delete s.access_token;try{n=Object.assign({},i,n,{data:s}),o=await uniCloud.httpclient.request(t,n)}catch(t){return function(e,t){throw new P({code:t.code||-2,message:t.message||e+" fail"})}(e,t)}let a=o.data;const c=o.headers["content-type"];if(!Buffer.isBuffer(a)||0!==c.indexOf("text/plain")&&0!==c.indexOf("application/json"))Buffer.isBuffer(a)&&(a={buffer:a,contentType:c});else try{a=JSON.parse(a.toString())}catch(e){a=a.toString()}return C(function(e,t){if(t.errcode)throw new P({code:t.errcode||-2,message:t.errmsg||e+" fail"});return delete t.errcode,delete t.errmsg,{...t,errMsg:e+" ok",errCode:0}}(e,a||{errCode:-2,errMsg:"Request failed"}))}function Y(e,t){let r="";if(t&&t.accessToken){r=`${e.indexOf("?")>-1?"&":"?"}access_token=${t.accessToken}`}return`${e}${r}`}class J{constructor(e){this.options=Object.assign({baseUrl:"https://api.weixin.qq.com",timeout:5e3},e)}async _requestWxOpenapi({name:e,url:t,data:r,options:n}){const i={method:"GET",dataType:"json",dataAsQueryString:!0,timeout:this.options.timeout};return await Q({name:"auth."+e,url:`${this.options.baseUrl}${Y(t,r)}`,data:r,options:n,defaultOptions:i})}async code2Session(e){return await this._requestWxOpenapi({name:"code2Session",url:"/sns/jscode2session",data:{grant_type:"authorization_code",appid:this.options.appId,secret:this.options.secret,js_code:e}})}async getOauthAccessToken(e){const t=await this._requestWxOpenapi({name:"getOauthAccessToken",url:"/sns/oauth2/access_token",data:{grant_type:"authorization_code",appid:this.options.appId,secret:this.options.secret,code:e}});return t.expiresIn&&(t.expired=Date.now()+t.expiresIn),t}}async function z({name:e,url:t,data:r,options:n,defaultOptions:i}){let o;n=Object.assign({},i,n,{data:T(Object.assign({},r))});try{o=await uniCloud.httpclient.request(t,n)}catch(t){return function(e,t){throw new P({code:t.code||-2,message:t.message||e+" fail"})}(e,t)}let s=o.data;const a=o.headers["content-type"];if(!Buffer.isBuffer(s)||0!==a.indexOf("text/plain")&&0!==a.indexOf("application/json"))Buffer.isBuffer(s)&&(s={buffer:s,contentType:a});else try{s=JSON.parse(s.toString())}catch(e){s=s.toString()}return C(function(e,t){if(t.ret||t.error){const r=t.ret||t.error||t.errcode||-2,n=t.msg||t.error_description||t.errmsg||e+" fail";throw new P({code:r,message:n})}return delete t.ret,delete t.msg,delete t.error,delete t.error_description,delete t.errcode,delete t.errmsg,{...t,errMsg:e+" ok",errCode:0}}(e,s||{errCode:-2,errMsg:"Request failed"}))}class W{constructor(e){this.options=Object.assign({baseUrl:"https://graph.qq.com",timeout:5e3},e)}async _requestQQOpenapi({name:e,url:t,data:r,options:n}){const i={method:"GET",dataType:"json",dataAsQueryString:!0,timeout:this.options.timeout};return await z({name:"auth."+e,url:k(this.options.baseUrl,t),data:r,options:n,defaultOptions:i})}async getOpenidByToken({accessToken:e}={}){const t=await this._requestQQOpenapi({name:"getOpenidByToken",url:"/oauth2.0/me",data:{accessToken:e,unionid:1,fmt:"json"}});if(t.clientId!==this.options.appId)throw new P({code:"APPID_NOT_MATCH",message:"获取openid失败,appid不匹配"});return{openid:t.openid,unionid:t.unionid}}async code2Session({code:e}={}){return await this._requestQQOpenapi({name:"getOpenidByToken",url:"https://api.q.qq.com/sns/jscode2session",data:{grant_type:"authorization_code",appid:this.options.appId,secret:this.options.secret,js_code:e}})}}const X={RSA:"RSA-SHA1",RSA2:"RSA-SHA256"};var Z={code2Session:{returnValue:{openid:"userId"}}};class ee extends class{constructor(e={}){if(!e.appId)throw new Error("appId required");if(!e.privateKey)throw new Error("privateKey required");const t={gateway:"https://openapi.alipay.com/gateway.do",timeout:5e3,charset:"utf-8",version:"1.0",signType:"RSA2",timeOffset:-(new Date).getTimezoneOffset()/60,keyType:"PKCS8"};e.sandbox&&(e.gateway="https://openapi.alipaydev.com/gateway.do"),this.options=Object.assign({},t,e);const r="PKCS8"===this.options.keyType?"PRIVATE KEY":"RSA PRIVATE KEY";this.options.privateKey=this._formatKey(this.options.privateKey,r),this.options.alipayPublicKey&&(this.options.alipayPublicKey=this._formatKey(this.options.alipayPublicKey,"PUBLIC KEY"))}_formatKey(e,t){return`-----BEGIN ${t}-----\n${e}\n-----END ${t}-----`}_formatUrl(e,t){let r=e;const n=["app_id","method","format","charset","sign_type","sign","timestamp","version","notify_url","return_url","auth_token","app_auth_token"];for(const e in t)if(n.indexOf(e)>-1){const n=encodeURIComponent(t[e]);r=`${r}${r.includes("?")?"&":"?"}${e}=${n}`,delete t[e]}return{execParams:t,url:r}}_getSign(e,t){const r=t.bizContent||null;delete t.bizContent;const i=Object.assign({method:e,appId:this.options.appId,charset:this.options.charset,version:this.options.version,signType:this.options.signType,timestamp:A((o=this.options.timeOffset,new Date(Date.now()+6e4*((new Date).getTimezoneOffset()+60*(o||0)))))},t);var o;r&&(i.bizContent=JSON.stringify(T(r)));const s=T(i),a=Object.keys(s).sort().map(e=>{let t=s[e];return"[object String]"!==Array.prototype.toString.call(t)&&(t=JSON.stringify(t)),`${e}=${t}`}).join("&"),c=n.createSign(X[this.options.signType]).update(a,"utf8").sign(this.options.privateKey,"base64");return Object.assign(s,{sign:c})}async _exec(e,t={},r={}){const n=this._getSign(e,t),{url:i,execParams:o}=this._formatUrl(this.options.gateway,n),{status:s,data:a}=await uniCloud.httpclient.request(i,{method:"POST",data:o,dataType:"text",timeout:this.options.timeout});if(200!==s)throw new Error("request fail");const c=JSON.parse(a),u=e.replace(/\./g,"_")+"_response",d=c[u],l=c.error_response;if(d){if(!r.validateSign||this._checkResponseSign(a,u)){if(!d.code||"10000"===d.code){return{errCode:0,errMsg:d.msg||"",...C(d)}}const e=d.sub_code?`${d.sub_code} ${d.sub_msg}`:""+(d.msg||"unkonwn error");throw new Error(e)}throw new Error("返回结果签名错误")}if(l)throw new Error(l.sub_msg||l.msg||"接口返回错误");throw new Error("request fail")}_checkResponseSign(e,t){if(!this.options.alipayPublicKey||""===this.options.alipayPublicKey)return console.warn("options.alipayPublicKey is empty"),!0;if(!e)return!1;const r=this._getSignStr(e,t),i=JSON.parse(e).sign,o=n.createVerify(X[this.options.signType]);return o.update(r,"utf8"),o.verify(this.options.alipayPublicKey,i,"base64")}_getSignStr(e,t){let r=e.trim();const n=e.indexOf(t+'"'),i=e.lastIndexOf('"sign"');return r=r.substr(n+t.length+1),r=r.substr(0,i),r=r.replace(/^[^{]*{/g,"{"),r=r.replace(/\}([^}]*)$/g,"}"),r}_notifyRSACheck(e,t,r){const i=Object.keys(e).sort().filter(e=>e).map(t=>{let r=e[t];return"[object String]"!==Array.prototype.toString.call(r)&&(r=JSON.stringify(r)),`${t}=${decodeURIComponent(r)}`}).join("&");return n.createVerify(X[r]).update(i,"utf8").verify(this.options.alipayPublicKey,t,"base64")}_checkNotifySign(e){const t=e.sign;if(!this.options.alipayPublicKey||!t)return!1;const r=e.sign_type||this.options.signType||"RSA2",n={...e};delete n.sign,n.sign_type=r;return!!this._notifyRSACheck(n,t,r)||(delete n.sign_type,this._notifyRSACheck(n,t,r))}_verifyNotify(e){if(!e.headers)throw new Error("通知格式不正确");let t;for(const r in e.headers)"content-type"===r.toLowerCase()&&(t=e.headers[r]);if(!1!==e.isBase64Encoded&&-1===t.indexOf("application/x-www-form-urlencoded"))throw new Error("通知格式不正确");const r=i.parse(e.body);if(this._checkNotifySign(r))return C(r);throw new Error("通知验签未通过")}}{constructor(e){super(e),this._protocols=Z}async code2Session(e){return await this._exec("alipay.system.oauth.token",{grantType:"authorization_code",code:e})}}function te(e){var t=e[0];return t<"0"||t>"7"?"00"+e:e}function re(e){var t=e.toString(16);return t.length%2?"0"+t:t}function ne(e){if(e<=127)return re(e);var t=re(e);return re(128+t.length/2)+t}function ie(e,t){return e(t={exports:{}},t.exports),t.exports}var oe=ie((function(e,t){var r=o.Buffer;function n(e,t){for(var r in e)t[r]=e[r]}function i(e,t,n){return r(e,t,n)}r.from&&r.alloc&&r.allocUnsafe&&r.allocUnsafeSlow?e.exports=o:(n(o,t),t.Buffer=i),i.prototype=Object.create(r.prototype),n(r,i),i.from=function(e,t,n){if("number"==typeof e)throw new TypeError("Argument must not be a number");return r(e,t,n)},i.alloc=function(e,t,n){if("number"!=typeof e)throw new TypeError("Argument must be a number");var i=r(e);return void 0!==t?"string"==typeof n?i.fill(t,n):i.fill(t):i.fill(0),i},i.allocUnsafe=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return r(e)},i.allocUnsafeSlow=function(e){if("number"!=typeof e)throw new TypeError("Argument must be a number");return o.SlowBuffer(e)}})),se=(oe.Buffer,oe.Buffer);function ae(e){if(this.buffer=null,this.writable=!0,this.readable=!0,!e)return this.buffer=se.alloc(0),this;if("function"==typeof e.pipe)return this.buffer=se.alloc(0),e.pipe(this),this;if(e.length||"object"==typeof e)return this.buffer=e,this.writable=!1,process.nextTick(function(){this.emit("end",e),this.readable=!1,this.emit("close")}.bind(this)),this;throw new TypeError("Unexpected data type ("+typeof e+")")}a.inherits(ae,s),ae.prototype.write=function(e){this.buffer=se.concat([this.buffer,se.from(e)]),this.emit("data",e)},ae.prototype.end=function(e){e&&this.write(e),this.emit("end",e),this.emit("close"),this.writable=!1,this.readable=!1};var ce=ae,ue=o.Buffer,de=o.SlowBuffer,le=pe;function pe(e,t){if(!ue.isBuffer(e)||!ue.isBuffer(t))return!1;if(e.length!==t.length)return!1;for(var r=0,n=0;n=128&&--n,n}var be={derToJose:function(e,t){e=_e(e);var r=ye(t),n=r+1,i=e.length,o=0;if(48!==e[o++])throw new Error('Could not find expected "seq"');var s=e[o++];if(129===s&&(s=e[o++]),i-o0)return function(e){if((e=String(e)).length>100)return;var t=/^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(e);if(!t)return;var r=parseFloat(t[1]);switch((t[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return 315576e5*r;case"weeks":case"week":case"w":return 6048e5*r;case"days":case"day":case"d":return r*yt;case"hours":case"hour":case"hrs":case"hr":case"h":return r*gt;case"minutes":case"minute":case"mins":case"min":case"m":return r*ht;case"seconds":case"second":case"secs":case"sec":case"s":return r*mt;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return r;default:return}}(e);if("number"===r&&isFinite(e))return t.long?function(e){var t=Math.abs(e);if(t>=yt)return _t(e,t,yt,"day");if(t>=gt)return _t(e,t,gt,"hour");if(t>=ht)return _t(e,t,ht,"minute");if(t>=mt)return _t(e,t,mt,"second");return e+" ms"}(e):function(e){var t=Math.abs(e);if(t>=yt)return Math.round(e/yt)+"d";if(t>=gt)return Math.round(e/gt)+"h";if(t>=ht)return Math.round(e/ht)+"m";if(t>=mt)return Math.round(e/mt)+"s";return e+"ms"}(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))};function _t(e,t,r,n){var i=t>=1.5*r;return Math.round(e/r)+" "+n+(i?"s":"")}var vt=function(e,t){var r=t||Math.floor(Date.now()/1e3);if("string"==typeof e){var n=wt(e);if(void 0===n)return;return Math.floor(r+n/1e3)}return"number"==typeof e?r+e:void 0},bt=ie((function(e,t){var r;t=e.exports=G,r="object"==typeof process&&process.env&&process.env.NODE_DEBUG&&/\bsemver\b/i.test(process.env.NODE_DEBUG)?function(){var e=Array.prototype.slice.call(arguments,0);e.unshift("SEMVER"),console.log.apply(console,e)}:function(){},t.SEMVER_SPEC_VERSION="2.0.0";var n=Number.MAX_SAFE_INTEGER||9007199254740991,i=t.re=[],o=t.src=[],s=0,a=s++;o[a]="0|[1-9]\\d*";var c=s++;o[c]="[0-9]+";var u=s++;o[u]="\\d*[a-zA-Z-][a-zA-Z0-9-]*";var d=s++;o[d]="("+o[a]+")\\.("+o[a]+")\\.("+o[a]+")";var l=s++;o[l]="("+o[c]+")\\.("+o[c]+")\\.("+o[c]+")";var p=s++;o[p]="(?:"+o[a]+"|"+o[u]+")";var f=s++;o[f]="(?:"+o[c]+"|"+o[u]+")";var m=s++;o[m]="(?:-("+o[p]+"(?:\\."+o[p]+")*))";var h=s++;o[h]="(?:-?("+o[f]+"(?:\\."+o[f]+")*))";var g=s++;o[g]="[0-9A-Za-z-]+";var y=s++;o[y]="(?:\\+("+o[g]+"(?:\\."+o[g]+")*))";var w=s++,_="v?"+o[d]+o[m]+"?"+o[y]+"?";o[w]="^"+_+"$";var v="[v=\\s]*"+o[l]+o[h]+"?"+o[y]+"?",b=s++;o[b]="^"+v+"$";var E=s++;o[E]="((?:<|>)?=?)";var C=s++;o[C]=o[c]+"|x|X|\\*";var T=s++;o[T]=o[a]+"|x|X|\\*";var A=s++;o[A]="[v=\\s]*("+o[T]+")(?:\\.("+o[T]+")(?:\\.("+o[T]+")(?:"+o[m]+")?"+o[y]+"?)?)?";var S=s++;o[S]="[v=\\s]*("+o[C]+")(?:\\.("+o[C]+")(?:\\.("+o[C]+")(?:"+o[h]+")?"+o[y]+"?)?)?";var I=s++;o[I]="^"+o[E]+"\\s*"+o[A]+"$";var x=s++;o[x]="^"+o[E]+"\\s*"+o[S]+"$";var k=s++;o[k]="(?:^|[^\\d])(\\d{1,16})(?:\\.(\\d{1,16}))?(?:\\.(\\d{1,16}))?(?:$|[^\\d])";var O=s++;o[O]="(?:~>?)";var R=s++;o[R]="(\\s*)"+o[O]+"\\s+",i[R]=new RegExp(o[R],"g");var P=s++;o[P]="^"+o[O]+o[A]+"$";var D=s++;o[D]="^"+o[O]+o[S]+"$";var j=s++;o[j]="(?:\\^)";var N=s++;o[N]="(\\s*)"+o[j]+"\\s+",i[N]=new RegExp(o[N],"g");var U=s++;o[U]="^"+o[j]+o[A]+"$";var V=s++;o[V]="^"+o[j]+o[S]+"$";var M=s++;o[M]="^"+o[E]+"\\s*("+v+")$|^$";var L=s++;o[L]="^"+o[E]+"\\s*("+_+")$|^$";var B=s++;o[B]="(\\s*)"+o[E]+"\\s*("+v+"|"+o[A]+")",i[B]=new RegExp(o[B],"g");var q=s++;o[q]="^\\s*("+o[A]+")\\s+-\\s+("+o[A]+")\\s*$";var $=s++;o[$]="^\\s*("+o[S]+")\\s+-\\s+("+o[S]+")\\s*$";var F=s++;o[F]="(<|>)?=?\\s*\\*";for(var K=0;K<35;K++)r(K,o[K]),i[K]||(i[K]=new RegExp(o[K]));function H(e,t){if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof G)return e;if("string"!=typeof e)return null;if(e.length>256)return null;if(!(t.loose?i[b]:i[w]).test(e))return null;try{return new G(e,t)}catch(e){return null}}function G(e,t){if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof G){if(e.loose===t.loose)return e;e=e.version}else if("string"!=typeof e)throw new TypeError("Invalid Version: "+e);if(e.length>256)throw new TypeError("version is longer than 256 characters");if(!(this instanceof G))return new G(e,t);r("SemVer",e,t),this.options=t,this.loose=!!t.loose;var o=e.trim().match(t.loose?i[b]:i[w]);if(!o)throw new TypeError("Invalid Version: "+e);if(this.raw=e,this.major=+o[1],this.minor=+o[2],this.patch=+o[3],this.major>n||this.major<0)throw new TypeError("Invalid major version");if(this.minor>n||this.minor<0)throw new TypeError("Invalid minor version");if(this.patch>n||this.patch<0)throw new TypeError("Invalid patch version");o[4]?this.prerelease=o[4].split(".").map((function(e){if(/^[0-9]+$/.test(e)){var t=+e;if(t>=0&&t=0;)"number"==typeof this.prerelease[r]&&(this.prerelease[r]++,r=-2);-1===r&&this.prerelease.push(0)}t&&(this.prerelease[0]===t?isNaN(this.prerelease[1])&&(this.prerelease=[t,0]):this.prerelease=[t,0]);break;default:throw new Error("invalid increment argument: "+e)}return this.format(),this.raw=this.version,this},t.inc=function(e,t,r,n){"string"==typeof r&&(n=r,r=void 0);try{return new G(e,r).inc(t,n).version}catch(e){return null}},t.diff=function(e,t){if(X(e,t))return null;var r=H(e),n=H(t),i="";if(r.prerelease.length||n.prerelease.length){i="pre";var o="prerelease"}for(var s in r)if(("major"===s||"minor"===s||"patch"===s)&&r[s]!==n[s])return i+s;return o},t.compareIdentifiers=Y;var Q=/^[0-9]+$/;function Y(e,t){var r=Q.test(e),n=Q.test(t);return r&&n&&(e=+e,t=+t),e===t?0:r&&!n?-1:n&&!r?1:e0}function W(e,t,r){return J(e,t,r)<0}function X(e,t,r){return 0===J(e,t,r)}function Z(e,t,r){return 0!==J(e,t,r)}function ee(e,t,r){return J(e,t,r)>=0}function te(e,t,r){return J(e,t,r)<=0}function re(e,t,r,n){switch(t){case"===":return"object"==typeof e&&(e=e.version),"object"==typeof r&&(r=r.version),e===r;case"!==":return"object"==typeof e&&(e=e.version),"object"==typeof r&&(r=r.version),e!==r;case"":case"=":case"==":return X(e,r,n);case"!=":return Z(e,r,n);case">":return z(e,r,n);case">=":return ee(e,r,n);case"<":return W(e,r,n);case"<=":return te(e,r,n);default:throw new TypeError("Invalid operator: "+t)}}function ne(e,t){if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof ne){if(e.loose===!!t.loose)return e;e=e.value}if(!(this instanceof ne))return new ne(e,t);r("comparator",e,t),this.options=t,this.loose=!!t.loose,this.parse(e),this.semver===ie?this.value="":this.value=this.operator+this.semver.version,r("comp",this)}t.rcompareIdentifiers=function(e,t){return Y(t,e)},t.major=function(e,t){return new G(e,t).major},t.minor=function(e,t){return new G(e,t).minor},t.patch=function(e,t){return new G(e,t).patch},t.compare=J,t.compareLoose=function(e,t){return J(e,t,!0)},t.rcompare=function(e,t,r){return J(t,e,r)},t.sort=function(e,r){return e.sort((function(e,n){return t.compare(e,n,r)}))},t.rsort=function(e,r){return e.sort((function(e,n){return t.rcompare(e,n,r)}))},t.gt=z,t.lt=W,t.eq=X,t.neq=Z,t.gte=ee,t.lte=te,t.cmp=re,t.Comparator=ne;var ie={};function oe(e,t){if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),e instanceof oe)return e.loose===!!t.loose&&e.includePrerelease===!!t.includePrerelease?e:new oe(e.raw,t);if(e instanceof ne)return new oe(e.value,t);if(!(this instanceof oe))return new oe(e,t);if(this.options=t,this.loose=!!t.loose,this.includePrerelease=!!t.includePrerelease,this.raw=e,this.set=e.split(/\s*\|\|\s*/).map((function(e){return this.parseRange(e.trim())}),this).filter((function(e){return e.length})),!this.set.length)throw new TypeError("Invalid SemVer Range: "+e);this.format()}function se(e){return!e||"x"===e.toLowerCase()||"*"===e}function ae(e,t,r,n,i,o,s,a,c,u,d,l,p){return((t=se(r)?"":se(n)?">="+r+".0.0":se(i)?">="+r+"."+n+".0":">="+t)+" "+(a=se(c)?"":se(u)?"<"+(+c+1)+".0.0":se(d)?"<"+c+"."+(+u+1)+".0":l?"<="+c+"."+u+"."+d+"-"+l:"<="+a)).trim()}function ce(e,t,n){for(var i=0;i0){var o=e[i].semver;if(o.major===t.major&&o.minor===t.minor&&o.patch===t.patch)return!0}return!1}return!0}function ue(e,t,r){try{t=new oe(t,r)}catch(e){return!1}return t.test(e)}function de(e,t,r,n){var i,o,s,a,c;switch(e=new G(e,n),t=new oe(t,n),r){case">":i=z,o=te,s=W,a=">",c=">=";break;case"<":i=W,o=ee,s=z,a="<",c="<=";break;default:throw new TypeError('Must provide a hilo val of "<" or ">"')}if(ue(e,t,n))return!1;for(var u=0;u=0.0.0")),l=l||e,p=p||e,i(e.semver,l.semver,n)?l=e:s(e.semver,p.semver,n)&&(p=e)})),l.operator===a||l.operator===c)return!1;if((!p.operator||p.operator===a)&&o(e,p.semver))return!1;if(p.operator===c&&s(e,p.semver))return!1}return!0}ne.prototype.parse=function(e){var t=this.options.loose?i[M]:i[L],r=e.match(t);if(!r)throw new TypeError("Invalid comparator: "+e);this.operator=r[1],"="===this.operator&&(this.operator=""),r[2]?this.semver=new G(r[2],this.options.loose):this.semver=ie},ne.prototype.toString=function(){return this.value},ne.prototype.test=function(e){return r("Comparator.test",e,this.options.loose),this.semver===ie||("string"==typeof e&&(e=new G(e,this.options)),re(e,this.operator,this.semver,this.options))},ne.prototype.intersects=function(e,t){if(!(e instanceof ne))throw new TypeError("a Comparator is required");var r;if(t&&"object"==typeof t||(t={loose:!!t,includePrerelease:!1}),""===this.operator)return r=new oe(e.value,t),ue(this.value,r,t);if(""===e.operator)return r=new oe(this.value,t),ue(e.semver,r,t);var n=!(">="!==this.operator&&">"!==this.operator||">="!==e.operator&&">"!==e.operator),i=!("<="!==this.operator&&"<"!==this.operator||"<="!==e.operator&&"<"!==e.operator),o=this.semver.version===e.semver.version,s=!(">="!==this.operator&&"<="!==this.operator||">="!==e.operator&&"<="!==e.operator),a=re(this.semver,"<",e.semver,t)&&(">="===this.operator||">"===this.operator)&&("<="===e.operator||"<"===e.operator),c=re(this.semver,">",e.semver,t)&&("<="===this.operator||"<"===this.operator)&&(">="===e.operator||">"===e.operator);return n||i||o&&s||a||c},t.Range=oe,oe.prototype.format=function(){return this.range=this.set.map((function(e){return e.join(" ").trim()})).join("||").trim(),this.range},oe.prototype.toString=function(){return this.range},oe.prototype.parseRange=function(e){var t=this.options.loose;e=e.trim();var n=t?i[$]:i[q];e=e.replace(n,ae),r("hyphen replace",e),e=e.replace(i[B],"$1$2$3"),r("comparator trim",e,i[B]),e=(e=(e=e.replace(i[R],"$1~")).replace(i[N],"$1^")).split(/\s+/).join(" ");var o=t?i[M]:i[L],s=e.split(" ").map((function(e){return function(e,t){return r("comp",e,t),e=function(e,t){return e.trim().split(/\s+/).map((function(e){return function(e,t){r("caret",e,t);var n=t.loose?i[V]:i[U];return e.replace(n,(function(t,n,i,o,s){var a;return r("caret",e,t,n,i,o,s),se(n)?a="":se(i)?a=">="+n+".0.0 <"+(+n+1)+".0.0":se(o)?a="0"===n?">="+n+"."+i+".0 <"+n+"."+(+i+1)+".0":">="+n+"."+i+".0 <"+(+n+1)+".0.0":s?(r("replaceCaret pr",s),a="0"===n?"0"===i?">="+n+"."+i+"."+o+"-"+s+" <"+n+"."+i+"."+(+o+1):">="+n+"."+i+"."+o+"-"+s+" <"+n+"."+(+i+1)+".0":">="+n+"."+i+"."+o+"-"+s+" <"+(+n+1)+".0.0"):(r("no pr"),a="0"===n?"0"===i?">="+n+"."+i+"."+o+" <"+n+"."+i+"."+(+o+1):">="+n+"."+i+"."+o+" <"+n+"."+(+i+1)+".0":">="+n+"."+i+"."+o+" <"+(+n+1)+".0.0"),r("caret return",a),a}))}(e,t)})).join(" ")}(e,t),r("caret",e),e=function(e,t){return e.trim().split(/\s+/).map((function(e){return function(e,t){var n=t.loose?i[D]:i[P];return e.replace(n,(function(t,n,i,o,s){var a;return r("tilde",e,t,n,i,o,s),se(n)?a="":se(i)?a=">="+n+".0.0 <"+(+n+1)+".0.0":se(o)?a=">="+n+"."+i+".0 <"+n+"."+(+i+1)+".0":s?(r("replaceTilde pr",s),a=">="+n+"."+i+"."+o+"-"+s+" <"+n+"."+(+i+1)+".0"):a=">="+n+"."+i+"."+o+" <"+n+"."+(+i+1)+".0",r("tilde return",a),a}))}(e,t)})).join(" ")}(e,t),r("tildes",e),e=function(e,t){return r("replaceXRanges",e,t),e.split(/\s+/).map((function(e){return function(e,t){e=e.trim();var n=t.loose?i[x]:i[I];return e.replace(n,(function(t,n,i,o,s,a){r("xRange",e,t,n,i,o,s,a);var c=se(i),u=c||se(o),d=u||se(s);return"="===n&&d&&(n=""),c?t=">"===n||"<"===n?"<0.0.0":"*":n&&d?(u&&(o=0),s=0,">"===n?(n=">=",u?(i=+i+1,o=0,s=0):(o=+o+1,s=0)):"<="===n&&(n="<",u?i=+i+1:o=+o+1),t=n+i+"."+o+"."+s):u?t=">="+i+".0.0 <"+(+i+1)+".0.0":d&&(t=">="+i+"."+o+".0 <"+i+"."+(+o+1)+".0"),r("xRange return",t),t}))}(e,t)})).join(" ")}(e,t),r("xrange",e),e=function(e,t){return r("replaceStars",e,t),e.trim().replace(i[F],"")}(e,t),r("stars",e),e}(e,this.options)}),this).join(" ").split(/\s+/);return this.options.loose&&(s=s.filter((function(e){return!!e.match(o)}))),s=s.map((function(e){return new ne(e,this.options)}),this)},oe.prototype.intersects=function(e,t){if(!(e instanceof oe))throw new TypeError("a Range is required");return this.set.some((function(r){return r.every((function(r){return e.set.some((function(e){return e.every((function(e){return r.intersects(e,t)}))}))}))}))},t.toComparators=function(e,t){return new oe(e,t).set.map((function(e){return e.map((function(e){return e.value})).join(" ").trim().split(" ")}))},oe.prototype.test=function(e){if(!e)return!1;"string"==typeof e&&(e=new G(e,this.options));for(var t=0;t":0===t.prerelease.length?t.patch++:t.prerelease.push(0),t.raw=t.format();case"":case">=":r&&!z(r,t)||(r=t);break;case"<":case"<=":break;default:throw new Error("Unexpected operation: "+e.operator)}}))}if(r&&e.test(r))return r;return null},t.validRange=function(e,t){try{return new oe(e,t).range||"*"}catch(e){return null}},t.ltr=function(e,t,r){return de(e,t,"<",r)},t.gtr=function(e,t,r){return de(e,t,">",r)},t.outside=de,t.prerelease=function(e,t){var r=H(e,t);return r&&r.prerelease.length?r.prerelease:null},t.intersects=function(e,t,r){return e=new oe(e,r),t=new oe(t,r),e.intersects(t)},t.coerce=function(e){if(e instanceof G)return e;if("string"!=typeof e)return null;var t=e.match(i[k]);if(null==t)return null;return H(t[1]+"."+(t[2]||"0")+"."+(t[3]||"0"))}})),Et=(bt.SEMVER_SPEC_VERSION,bt.re,bt.src,bt.parse,bt.valid,bt.clean,bt.SemVer,bt.inc,bt.diff,bt.compareIdentifiers,bt.rcompareIdentifiers,bt.major,bt.minor,bt.patch,bt.compare,bt.compareLoose,bt.rcompare,bt.sort,bt.rsort,bt.gt,bt.lt,bt.eq,bt.neq,bt.gte,bt.lte,bt.cmp,bt.Comparator,bt.Range,bt.toComparators,bt.satisfies,bt.maxSatisfying,bt.minSatisfying,bt.minVersion,bt.validRange,bt.ltr,bt.gtr,bt.outside,bt.prerelease,bt.intersects,bt.coerce,bt.satisfies(process.version,"^6.12.0 || >=8.0.0")),Ct=["RS256","RS384","RS512","ES256","ES384","ES512"],Tt=["RS256","RS384","RS512"],At=["HS256","HS384","HS512"];Et&&(Ct.splice(3,0,"PS256","PS384","PS512"),Tt.splice(3,0,"PS256","PS384","PS512"));var St=/^\s+|\s+$/g,It=/^[-+]0x[0-9a-f]+$/i,xt=/^0b[01]+$/i,kt=/^0o[0-7]+$/i,Ot=/^(?:0|[1-9]\d*)$/,Rt=parseInt;function Pt(e){return e!=e}function Dt(e,t){return function(e,t){for(var r=-1,n=e?e.length:0,i=Array(n);++r-1&&e%1==0&&e-1&&e%1==0&&e<=9007199254740991}(e.length)&&!function(e){var t=Qt(e)?Mt.call(e):"";return"[object Function]"==t||"[object GeneratorFunction]"==t}(e)}function Qt(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function Yt(e){return!!e&&"object"==typeof e}var Jt=function(e,t,r,n){var i;e=Gt(e)?e:(i=e)?Dt(i,function(e){return Gt(e)?$t(e):Ft(e)}(i)):[],r=r&&!n?function(e){var t=function(e){if(!e)return 0===e?e:0;if((e=function(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||Yt(e)&&"[object Symbol]"==Mt.call(e)}(e))return NaN;if(Qt(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=Qt(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(St,"");var r=xt.test(e);return r||kt.test(e)?Rt(e.slice(2),r?2:8):It.test(e)?NaN:+e}(e))===1/0||e===-1/0){return 17976931348623157e292*(e<0?-1:1)}return e==e?e:0}(e),r=t%1;return t==t?r?t-r:t:0}(r):0;var o=e.length;return r<0&&(r=qt(o+r,0)),function(e){return"string"==typeof e||!Ht(e)&&Yt(e)&&"[object String]"==Mt.call(e)}(e)?r<=o&&e.indexOf(t,r)>-1:!!o&&function(e,t,r){if(t!=t)return function(e,t,r,n){for(var i=e.length,o=r+(n?1:-1);n?o--:++o-1},zt=Object.prototype.toString;var Wt=function(e){return!0===e||!1===e||function(e){return!!e&&"object"==typeof e}(e)&&"[object Boolean]"==zt.call(e)},Xt=/^\s+|\s+$/g,Zt=/^[-+]0x[0-9a-f]+$/i,er=/^0b[01]+$/i,tr=/^0o[0-7]+$/i,rr=parseInt,nr=Object.prototype.toString;function ir(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}var or=function(e){return"number"==typeof e&&e==function(e){var t=function(e){if(!e)return 0===e?e:0;if((e=function(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&"[object Symbol]"==nr.call(e)}(e))return NaN;if(ir(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=ir(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(Xt,"");var r=er.test(e);return r||tr.test(e)?rr(e.slice(2),r?2:8):Zt.test(e)?NaN:+e}(e))===1/0||e===-1/0){return 17976931348623157e292*(e<0?-1:1)}return e==e?e:0}(e),r=t%1;return t==t?r?t-r:t:0}(e)},sr=Object.prototype.toString;var ar=function(e){return"number"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&"[object Number]"==sr.call(e)};var cr=Function.prototype,ur=Object.prototype,dr=cr.toString,lr=ur.hasOwnProperty,pr=dr.call(Object),fr=ur.toString,mr=function(e,t){return function(r){return e(t(r))}}(Object.getPrototypeOf,Object);var hr=function(e){if(!function(e){return!!e&&"object"==typeof e}(e)||"[object Object]"!=fr.call(e)||function(e){var t=!1;if(null!=e&&"function"!=typeof e.toString)try{t=!!(e+"")}catch(e){}return t}(e))return!1;var t=mr(e);if(null===t)return!0;var r=lr.call(t,"constructor")&&t.constructor;return"function"==typeof r&&r instanceof r&&dr.call(r)==pr},gr=Object.prototype.toString,yr=Array.isArray;var wr=function(e){return"string"==typeof e||!yr(e)&&function(e){return!!e&&"object"==typeof e}(e)&&"[object String]"==gr.call(e)},_r=/^\s+|\s+$/g,vr=/^[-+]0x[0-9a-f]+$/i,br=/^0b[01]+$/i,Er=/^0o[0-7]+$/i,Cr=parseInt,Tr=Object.prototype.toString;function Ar(e,t){var r;if("function"!=typeof t)throw new TypeError("Expected a function");return e=function(e){var t=function(e){if(!e)return 0===e?e:0;if((e=function(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&"[object Symbol]"==Tr.call(e)}(e))return NaN;if(Sr(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=Sr(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(_r,"");var r=br.test(e);return r||Er.test(e)?Cr(e.slice(2),r?2:8):vr.test(e)?NaN:+e}(e))===1/0||e===-1/0){return 17976931348623157e292*(e<0?-1:1)}return e==e?e:0}(e),r=t%1;return t==t?r?t-r:t:0}(e),function(){return--e>0&&(r=t.apply(this,arguments)),e<=1&&(t=void 0),r}}function Sr(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}var Ir=function(e){return Ar(2,e)},xr=["RS256","RS384","RS512","ES256","ES384","ES512","HS256","HS384","HS512","none"];Et&&xr.splice(3,0,"PS256","PS384","PS512");var kr={expiresIn:{isValid:function(e){return or(e)||wr(e)&&e},message:'"expiresIn" should be a number of seconds or string representing a timespan'},notBefore:{isValid:function(e){return or(e)||wr(e)&&e},message:'"notBefore" should be a number of seconds or string representing a timespan'},audience:{isValid:function(e){return wr(e)||Array.isArray(e)},message:'"audience" must be a string or array'},algorithm:{isValid:Jt.bind(null,xr),message:'"algorithm" must be a valid string enum value'},header:{isValid:hr,message:'"header" must be an object'},encoding:{isValid:wr,message:'"encoding" must be a string'},issuer:{isValid:wr,message:'"issuer" must be a string'},subject:{isValid:wr,message:'"subject" must be a string'},jwtid:{isValid:wr,message:'"jwtid" must be a string'},noTimestamp:{isValid:Wt,message:'"noTimestamp" must be a boolean'},keyid:{isValid:wr,message:'"keyid" must be a string'},mutatePayload:{isValid:Wt,message:'"mutatePayload" must be a boolean'}},Or={iat:{isValid:ar,message:'"iat" should be a number of seconds'},exp:{isValid:ar,message:'"exp" should be a number of seconds'},nbf:{isValid:ar,message:'"nbf" should be a number of seconds'}};function Rr(e,t,r,n){if(!hr(r))throw new Error('Expected "'+n+'" to be a plain object.');Object.keys(r).forEach((function(i){var o=e[i];if(o){if(!o.isValid(r[i]))throw new Error(o.message)}else if(!t)throw new Error('"'+i+'" is not allowed in "'+n+'"')}))}var Pr={audience:"aud",issuer:"iss",subject:"sub",jwtid:"jti"},Dr=["expiresIn","notBefore","noTimestamp","audience","issuer","subject","jwtid"],jr=function(e,t,r,n){var i;if("function"!=typeof r||n||(n=r,r={}),r||(r={}),r=Object.assign({},r),i=n||function(e,t){if(e)throw e;return t},r.clockTimestamp&&"number"!=typeof r.clockTimestamp)return i(new ut("clockTimestamp must be a number"));if(void 0!==r.nonce&&("string"!=typeof r.nonce||""===r.nonce.trim()))return i(new ut("nonce must be a non-empty string"));var o=r.clockTimestamp||Math.floor(Date.now()/1e3);if(!e)return i(new ut("jwt must be provided"));if("string"!=typeof e)return i(new ut("jwt must be a string"));var s,a=e.split(".");if(3!==a.length)return i(new ut("jwt malformed"));try{s=at(e,{complete:!0})}catch(e){return i(e)}if(!s)return i(new ut("invalid token"));var c,u=s.header;if("function"==typeof t){if(!n)return i(new ut("verify must be called asynchronous if secret or public key is provided as a callback"));c=t}else c=function(e,r){return r(null,t)};return c(u,(function(t,n){if(t)return i(new ut("error in secret or public key callback: "+t.message));var c,d=""!==a[2].trim();if(!d&&n)return i(new ut("jwt signature is required"));if(d&&!n)return i(new ut("secret or public key must be provided"));if(d||r.algorithms||(r.algorithms=["none"]),r.algorithms||(r.algorithms=~n.toString().indexOf("BEGIN CERTIFICATE")||~n.toString().indexOf("BEGIN PUBLIC KEY")?Ct:~n.toString().indexOf("BEGIN RSA PUBLIC KEY")?Tt:At),!~r.algorithms.indexOf(s.header.alg))return i(new ut("invalid algorithm"));try{c=st.verify(e,s.header.alg,n)}catch(e){return i(e)}if(!c)return i(new ut("invalid signature"));var l=s.payload;if(void 0!==l.nbf&&!r.ignoreNotBefore){if("number"!=typeof l.nbf)return i(new ut("invalid nbf value"));if(l.nbf>o+(r.clockTolerance||0))return i(new lt("jwt not active",new Date(1e3*l.nbf)))}if(void 0!==l.exp&&!r.ignoreExpiration){if("number"!=typeof l.exp)return i(new ut("invalid exp value"));if(o>=l.exp+(r.clockTolerance||0))return i(new ft("jwt expired",new Date(1e3*l.exp)))}if(r.audience){var p=Array.isArray(r.audience)?r.audience:[r.audience];if(!(Array.isArray(l.aud)?l.aud:[l.aud]).some((function(e){return p.some((function(t){return t instanceof RegExp?t.test(e):t===e}))})))return i(new ut("jwt audience invalid. expected: "+p.join(" or ")))}if(r.issuer&&("string"==typeof r.issuer&&l.iss!==r.issuer||Array.isArray(r.issuer)&&-1===r.issuer.indexOf(l.iss)))return i(new ut("jwt issuer invalid. expected: "+r.issuer));if(r.subject&&l.sub!==r.subject)return i(new ut("jwt subject invalid. expected: "+r.subject));if(r.jwtid&&l.jti!==r.jwtid)return i(new ut("jwt jwtid invalid. expected: "+r.jwtid));if(r.nonce&&l.nonce!==r.nonce)return i(new ut("jwt nonce invalid. expected: "+r.nonce));if(r.maxAge){if("number"!=typeof l.iat)return i(new ut("iat required when maxAge is specified"));var f=vt(r.maxAge,l.iat);if(void 0===f)return i(new ut('"maxAge" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'));if(o>=f+(r.clockTolerance||0))return i(new ft("maxAge exceeded",new Date(1e3*f)))}if(!0===r.complete){var m=s.signature;return i(null,{header:u,payload:l,signature:m})}return i(null,l)}))},Nr=function(e,t,r,n){"function"==typeof r?(n=r,r={}):r=r||{};var i="object"==typeof e&&!Buffer.isBuffer(e),o=Object.assign({alg:r.algorithm||"HS256",typ:i?"JWT":void 0,kid:r.keyid},r.header);function s(e){if(n)return n(e);throw e}if(!t&&"none"!==r.algorithm)return s(new Error("secretOrPrivateKey must have a value"));if(void 0===e)return s(new Error("payload is required"));if(i){try{!function(e){Rr(Or,!0,e,"payload")}(e)}catch(e){return s(e)}r.mutatePayload||(e=Object.assign({},e))}else{var a=Dr.filter((function(e){return void 0!==r[e]}));if(a.length>0)return s(new Error("invalid "+a.join(",")+" option for "+typeof e+" payload"))}if(void 0!==e.exp&&void 0!==r.expiresIn)return s(new Error('Bad "options.expiresIn" option the payload already has an "exp" property.'));if(void 0!==e.nbf&&void 0!==r.notBefore)return s(new Error('Bad "options.notBefore" option the payload already has an "nbf" property.'));try{!function(e){Rr(kr,!1,e,"options")}(r)}catch(e){return s(e)}var c=e.iat||Math.floor(Date.now()/1e3);if(r.noTimestamp?delete e.iat:i&&(e.iat=c),void 0!==r.notBefore){try{e.nbf=vt(r.notBefore,c)}catch(e){return s(e)}if(void 0===e.nbf)return s(new Error('"notBefore" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'))}if(void 0!==r.expiresIn&&"object"==typeof e){try{e.exp=vt(r.expiresIn,c)}catch(e){return s(e)}if(void 0===e.exp)return s(new Error('"expiresIn" should be a number of seconds or string representing a timespan eg: "1d", "20h", 60'))}Object.keys(Pr).forEach((function(t){var n=Pr[t];if(void 0!==r[t]){if(void 0!==e[n])return s(new Error('Bad "options.'+t+'" option. The payload already has an "'+n+'" property.'));e[n]=r[t]}}));var u=r.encoding||"utf8";if("function"!=typeof n)return st.sign({header:o,payload:e,secret:t,encoding:u});n=n&&Ir(n),st.createSign({header:o,privateKey:t,payload:e,encoding:u}).once("error",n).once("done",(function(e){n(null,e)}))};let Ur=[];class Vr{constructor(e){this.options=Object.assign({baseUrl:"https://appleid.apple.com",timeout:1e4},e)}async _fetch(e,t){const{baseUrl:r}=this.options;return uniCloud.httpclient.request(r+e,t)}async verifyIdentityToken(e){const t=e.split(".")[0],{kid:r}=JSON.parse(Buffer.from(t,"base64").toString());if(!Ur.length)try{Ur=await this.getAuthKeys()}catch(e){return{code:10705,msg:"Apple 公钥获取失败,请重新登录:"+e.message,messageValues:{account:"苹果账号"}}}const n=this.getUsedKey(Ur,r);if(!Object.keys(n).length)return Ur.length=0,{code:10705,msg:"Apple 公钥获取失败,请重新登录:usedKey",messageValues:{account:"苹果账号"}};let i=null;try{i=jr(e,function(e,t){var r=Buffer.from(e,"base64"),n=Buffer.from(t,"base64"),i=r.toString("hex"),o=n.toString("hex");i=te(i),o=te(o);var s=i.length/2,a=o.length/2,c=ne(s),u=ne(a),d="30"+ne(s+a+c.length/2+u.length/2+2)+"02"+c+i+"02"+u+o;return"-----BEGIN RSA PUBLIC KEY-----\n"+Buffer.from(d,"hex").toString("base64").match(/.{1,64}/g).join("\n")+"\n-----END RSA PUBLIC KEY-----\n"}(n.n,n.e),{algorithms:n.alg})}catch(e){return{code:10705,msg:"JWT 校验失败,请重新登录:"+e.message,messageValues:{account:"苹果账号"}}}return{code:0,msg:i}}async getAuthKeys(){const{status:e,data:t}=await this._fetch("/auth/keys",{method:"GET",dataType:"json",timeout:this.options.timeout});if(200!==e)throw new Error("request https://appleid.apple.com/auth/keys fail");return t.keys}getUsedKey(e,t){let r={};for(let n=0;nvoid 0===e))return{code:F,messageValues:{param:"应用Appid"}};const r=await M.doc(e).get(),n=r&&r.data&&r.data[0];if(!n)return{code:10002};const i=Object.keys($),o=i.reduce((e,t)=>{const r=t,i=function(e,t){return t.split(".").reduce((e,t)=>e&&e[t],e)}(n,t);return i&&e.push({[r]:i}),e},[]);let s;const a={dcloud_appid:Fr.in(t),_id:Fr.neq(n._id)},c={dcloud_appid:Fr.exists(!1),_id:Fr.neq(n._id)};switch(o.length){case 0:return{code:10004,message:"用户缺少唯一标识(包括但不限于用户名、手机号、邮箱)"};case 1:s=Fr.or([Fr.and([o[0],a]),Fr.and([o[0],c])]);break;default:s=Fr.or([Fr.and([Fr.or(o),a]),Fr.and([Fr.or(o),c])])}const u=await M.where(s).limit(1).get(),d=u&&u.data&&u.data[0];if(!d)return{code:0};const l=i.find(e=>n[e]===d[e]);return{code:10005,msg:`此用户的${$[l]}已被授权登录,不可再次授权`}}const Hr=uniCloud.database().command;const Gr=uniCloud.database();const Qr=uniCloud.database();const Yr=uniCloud.database();async function Jr(e){const t=["apiKey","apiSecret"];for(let r=0,n=t.length;r upRes",await M.doc(e.uid).update({avatar:e.avatar})),{code:0,msg:"头像设置成功"}},updatePwd:async function(e){const t=await M.doc(e.uid).get();if(t&&t.data&&t.data.length>0){if(0===this._checkPwd(t.data[0],e.oldPassword).code){const{passwordHash:r,version:n}=this.encryptPwd(e.newPassword),i={password:r,token:[]};n&&(i.password_secret_version=n);return S("upRes",await M.doc(t.data[0]._id).update(i)),{code:0,msg:"修改成功"}}return{code:40202,msg:"旧密码错误"}}return{code:40201}},updateUser:async function(e){const t=e.uid;if(!t)return{code:F,messageValues:{param:"用户Id"}};delete e.uid;const{username:r,email:n}=e,{usernameToLowerCase:i,emailToLowerCase:o}=this._getConfig();let s=r&&r.trim(),a=n&&n.trim();return s&&(i&&(s=s.toLowerCase()),e.username=s),a&&(o&&(a=a.toLowerCase()),e.email=a),S("update -> upRes",await M.doc(t).update(e)),{code:0,msg:"修改成功"}},_getAlipayApi:function({platform:e}={}){const t=e||this.context.PLATFORM;if(!t)throw new Error("未能获取客户端平台信息,请主动传入platform");const r=this._getConfig(t);if(!r.oauth||!r.oauth.alipay)throw new Error(`请在公用模块uni-id的config.json中添加${t}平台支付宝登录配置项`);return["appid","privateKey"].forEach(e=>{if(!r.oauth.alipay[e])throw new Error(`请在公用模块uni-id的config.json中添加配置项:${t}.oauth.alipay.${e}`)}),Br({...r.oauth.alipay,clientType:t})},_getValidInviteCode:async function({inviteCode:e}){let t,r=10;e?(r=1,t=e):t=$r();let n=!1;try{for(;r>0&&!n;){r--;if(0===(await M.where({my_invite_code:t}).get()).data.length){n=!0;break}t=$r()}return n?{code:0,inviteCode:t}:e?{code:80401,msg:"邀请码重复,设置失败"}:{code:80402,msg:"邀请码设置失败稍后再试"}}catch(e){return{code:90001,msg:"数据库读写异常"}}},_addUser:async function(e,{needPermission:t,autoSetDcloudAppid:r=!0}={}){const n=this._getConfig(),i={...e,dcloud_appid:r?[this.context.APPID]:[],register_date:Date.now(),register_ip:this.context.CLIENTIP},o=(await M.add(i)).id;let s;if(n.removePermissionAndRoleFromToken)s=await this.createToken({uid:o,needPermission:t});else{const t=e.role||[];let r;r=0===t.length||t.includes("admin")?[]:await this._getPermissionListByRoleList(t),s=await this.createToken({uid:o,role:t,permission:r})}const{token:a,tokenExpired:c}=s;return await M.doc(o).update({token:[a]}),{token:a,tokenExpired:c,uid:o,type:"register",userInfo:Object.assign({},i,{token:a})}},_loginExec:async function(e,t={}){if(1===e.status)return{code:10001,msg:"账号已禁用"};const r=this._getConfig();let n=e.token||[];"string"==typeof n&&(n=[n]);const i=this._getExpiredToken(n);let o;if(n=n.filter(e=>-1===i.indexOf(e)),r.removePermissionAndRoleFromToken){const r=t.needPermission;o=await this.createToken({uid:e._id,needPermission:r})}else{const t=e.role||[];let r;r=0===t.length||t.includes("admin")?[]:await this._getPermissionListByRoleList(t),o=await this.createToken({uid:e._id,role:t,permission:r})}const{token:s,tokenExpired:a}=o;n.push(s),e.token=n;const c={last_login_date:Date.now(),last_login_ip:this.context.CLIENTIP,token:n,...t.extraData};await M.doc(e._id).update(c);const u=Object.assign({},e,c);return{code:0,msg:"登录成功",token:s,uid:u._id,username:u.username,type:"login",userInfo:u,tokenExpired:a}},_registerExec:async function(e,{needPermission:t,autoSetDcloudAppid:r=!0}={}){const{my_invite_code:n}=e;if(this._getConfig().autoSetInviteCode||n){const t=await this._getValidInviteCode({inviteCode:n});if(t.code)return t;e.my_invite_code=t.inviteCode}return{code:0,msg:"注册成功",...await this._addUser(e,{needPermission:t,autoSetDcloudAppid:r})}},_getWeixinApi:function({platform:e}={}){const t=e||this.context.PLATFORM;if(!t)throw new Error("未能获取客户端平台信息,请主动传入platform");const r=this._getConfig(t);if(!r.oauth||!r.oauth.weixin)throw new Error(`请在公用模块uni-id的config.json中添加${t}平台微信登录配置项`);return["appid","appsecret"].forEach(e=>{if(!r.oauth.weixin[e])throw new Error(`请在公用模块uni-id的config.json中添加配置项:${t}.oauth.weixin.${e}`)}),Mr({...r.oauth.weixin,clientType:t})},_getQQApi:function(){const e=this.context.PLATFORM;if(!e)throw new Error("未能获取客户端平台信息,请主动传入platform");const t=this._getConfig(e);if(!t.oauth||!t.oauth.qq)throw new Error(`请在公用模块uni-id的config.json中添加${e}平台QQ登录配置项`);return["appid","appsecret"].forEach(r=>{if(!t.oauth.qq[r])throw new Error(`请在公用模块uni-id的config.json中添加配置项:${e}.oauth.qq.${r}`)}),Lr({...t.oauth.qq,clientType:e})},_getMatchedUser:function(e,t){if(0===e.length)return{code:10002};let r;const n={},i={};for(let r=e.length-1;r>=0;r--){const o=e[r];for(let s=0;s0?{code:10003,messageValues:{target:"用户"}}:{code:0,msg:"",userMatched:r,fieldMatched:s,isFallbackValueMatched:!!s&&i[s]}},_getCurrentAppUser:function(e){const t=this.context.APPID;return e.filter(e=>void 0===e.dcloud_appid||null===e.dcloud_appid||e.dcloud_appid.indexOf(t)>-1)},setAuthorizedAppLogin:async function({uid:e,dcloudAppidList:t}={}){if("array"!==y(t))return{code:K,messageValues:{param:"应用列表",reason:"应用列表必须是一个数组"}};if(t&&0!==t.length){const r=await Kr({uid:e,dcloudAppidList:t});if(r.code)return r}return await M.doc(e).update({dcloud_appid:Fr.set(t)}),{code:0}},authorizeAppLogin:async function({uid:e,dcloudAppid:t}={}){const r=await Kr({uid:e,dcloudAppidList:[t]});return r.code?r:(await M.doc(e).update({dcloud_appid:Fr.push(t)}),{code:0})},forbidAppLogin:async function({uid:e,dcloudAppid:t}={}){return e?(await M.doc(e).update({dcloud_appid:Fr.pull(t)}),{code:0}):{code:F,messageValues:{param:"用户ID"}}},acceptInvite:async function({uid:e,inviteCode:t}){const r=await M.where({_id:Hr.neq(e),inviter_uid:Hr.not(Hr.all([e])),my_invite_code:t}).get();if(1!==r.data.length)return{code:80501,msg:"邀请码无效"};const n=[r.data[0]._id].concat(r.data[0].inviter_uid||[]),i=await M.doc(e).field({my_invite_code:!0,inviter_uid:!0}).get();if(0===i.data.length)return{code:80502};if(i.data[0].inviter_uid&&i.data[0].inviter_uid.length>0)return{code:80503,msg:"邀请码不可修改"};const o=Date.now();return await M.doc(e).update({inviter_uid:n,invite_time:o}),await M.where({inviter_uid:e}).update({inviter_uid:Hr.push(n)}),{code:0,msg:"邀请码填写成功"}},getInvitedUser:async function({uid:e,level:t=1,limit:r=20,offset:n=0,needTotal:i=!1}){const o={code:0,msg:"获取邀请列表成功",invitedUser:(await M.where({["inviter_uid."+(t-1)]:e}).field({_id:!0,username:!0,mobile:!0,invite_time:!0}).orderBy("invite_time","desc").skip(n).limit(r).get()).data};if(i){const r=await M.where({["inviter_uid."+(t-1)]:e}).count();o.total=r.total}return o},setUserInviteCode:async function({uid:e,myInviteCode:t}){const r=await this._getValidInviteCode({inviteCode:t});return r.code?r:(await M.doc(e).update({my_invite_code:r.inviteCode}),{code:0,msg:"邀请码设置成功",myInviteCode:r.inviteCode})},loginByAlipay:async function(e){"string"==typeof e&&(e={code:e});const{needPermission:t,platform:r,code:n,myInviteCode:i,role:o,type:s}=e,a=r||this.context.PLATFORM,{openid:c}=await this._getAlipayApi({platform:a}).code2Session(n);if(!c)return{code:10501,messageValues:{account:"支付宝账号"}};let u=await M.where({ali_openid:c}).get();if(u=this._getCurrentAppUser(u.data),u&&u.length>0){if("register"===s)return{code:10502,messageValues:{type:"支付宝账号"}};const e=u[0],r=await this._loginExec(e,{needPermission:t});if(0!==r.code)return r;const{userInfo:n}=r;return{...r,openid:c,mobileConfirmed:1===n.mobile_confirmed,emailConfirmed:1===n.email_confirmed}}{if("login"===s)return{code:10503,messageValues:{type:"QQ账号"}};const e={ali_openid:c};e.my_invite_code=i,e.role=o;const r=await this._registerExec(e,{needPermission:t});return 0!==r.code?r:{...r,openid:c,mobileConfirmed:!1,emailConfirmed:!1}}},loginByEmail:async function({email:e,code:t,password:r,myInviteCode:n,type:i,needPermission:o,role:s}){if(!(e=e&&e.trim()))return{code:F,messageValues:{param:"邮箱"}};const{emailToLowerCase:a}=this._getConfig();let c=e;a&&(c=e.toLowerCase());const u=await this.verifyCode({email:c,code:t,type:i||"login"});if(0!==u.code)return u;let d={email:e,email_confirmed:1};const l={field:"email",value:e},p=Gr.command;c!==e&&(d=p.or(d,{email:c,email_confirmed:1}),l.fallbackValue=c);let f=await M.where(d).get();if(f=this._getCurrentAppUser(f.data),f&&f.length>0){if("register"===i)return{code:10301,messageValues:{type:"邮箱"}};const e=this._getMatchedUser(f,[l]);if(e.code)return e;const{userMatched:t}=e,r=await this._loginExec(t,{needPermission:o});return 0!==r.code?r:{...r,email:c}}{if("login"===i)return{code:10302,messageValues:{type:"邮箱"}};const e={email:c,email_confirmed:1},t=r&&r.trim();if(t){const{passwordHash:r,version:n}=this.encryptPwd(t);e.password=r,n&&(e.password_secret_version=n)}e.my_invite_code=n,e.role=s;const a=await this._registerExec(e,{needPermission:o});return 0!==a.code?a:{...a,email:c}}},loginBySms:async function({mobile:e,code:t,password:r,inviteCode:n,myInviteCode:i,type:o,needPermission:s,role:a}){if(!(e=e&&e.trim()))return{code:F,messageValues:{param:"手机号码"}};const c=this._getConfig();if(c.forceInviteCode&&!o)throw new Error("[loginBySms]强制使用邀请码注册时,需指明type为register还是login");const u=await this.verifyCode({mobile:e,code:t,type:o||"login"});if(0!==u.code)return u;const d={mobile:e,mobile_confirmed:1};let l=await M.where(d).get();if(l=this._getCurrentAppUser(l.data),l&&l.length>0){if("register"===o)return{code:10201,messageValues:{type:"手机号"}};const t=l[0],r=await this._loginExec(t,{needPermission:s});return 0!==r.code?r:{...r,mobile:e}}{const t=Date.now();if("login"===o)return{code:10202,messageValues:{type:"手机号"}};const u={mobile:e,mobile_confirmed:1,register_ip:this.context.CLIENTIP,register_date:t},d=r&&r.trim();if(d){const{passwordHash:e,version:t}=this.encryptPwd(d);u.password=e,t&&(u.password_secret_version=t)}if(n){const e=await M.where({my_invite_code:n}).get();if(1!==e.data.length)return{code:10203,msg:"邀请码无效"};u.inviter_uid=[e.data[0]._id].concat(e.data[0].inviter_uid||[]),u.invite_time=t}else if(c.forceInviteCode)return{code:10203,msg:"邀请码无效"};u.my_invite_code=i,u.role=a;const l=await this._registerExec(u,{needPermission:s});return 0!==l.code?l:{...l,mobile:e}}},loginByWeixin:async function(e){"string"==typeof e&&(e={code:e});const{needPermission:t,platform:r,code:n,myInviteCode:i,role:o,type:s}=e,a=r||this.context.PLATFORM,c="mp-weixin"===a,{openid:u,unionid:d,sessionKey:l,accessToken:p,refreshToken:f,expired:m}=await this._getWeixinApi({platform:a})[c?"code2Session":"getOauthAccessToken"](n);if(!u)return{code:10401,messageValues:{account:"微信openid"}};let h;h=c?{sessionKey:l}:{accessToken:p,refreshToken:f,accessTokenExpired:m};const g=Qr.command,y=[{wx_openid:{[a]:u}}];d&&y.push({wx_unionid:d});let w=await M.where(g.or(...y)).get();if(w=this._getCurrentAppUser(w.data),w&&w.length>0){if("register"===s)return{code:10402,messageValues:{type:"微信账号"}};const e=w[0],r={wx_openid:{[a]:u}};d&&(r.wx_unionid=d);const n=await this._loginExec(e,{needPermission:t,extraData:r});if(0!==n.code)return n;const{userInfo:i}=n;return{...n,openid:u,unionid:d,...h,mobileConfirmed:1===i.mobile_confirmed,emailConfirmed:1===i.email_confirmed}}{if("login"===s)return{code:10403,messageValues:{type:"微信账号"}};const e={wx_openid:{[a]:u},wx_unionid:d};e.my_invite_code=i,e.role=o;const r=await this._registerExec(e,{needPermission:t});return 0!==r.code?r:{...r,openid:u,unionid:d,...h,mobileConfirmed:!1,emailConfirmed:!1}}},loginByQQ:async function({code:e,accessToken:t,myInviteCode:r,needPermission:n,role:i,type:o}={}){const s=this.context.PLATFORM,a="mp-qq"===s,{openid:c,unionid:u,sessionKey:d}=await this._getQQApi()[a?"code2Session":"getOpenidByToken"]({code:e,accessToken:t});if(!c)return{code:10801,messageValues:{account:"qq openid"}};const l={accessToken:t,sessionKey:d},p=Yr.command,f=[{qq_openid:{[s]:c}}];u&&f.push({qq_unionid:u});let m=await M.where(p.or(...f)).get();if(m=this._getCurrentAppUser(m.data),m&&m.length>0){if("register"===o)return{code:10802,messageValues:{type:"QQ账号"}};const e=m[0],t={qq_openid:{[s]:c}};u&&(t.qq_unionid=u);const r=await this._loginExec(e,{needPermission:n,extraData:t});if(0!==r.code)return r;const{userInfo:i}=r;return{...r,openid:c,unionid:u,...l,mobileConfirmed:1===i.mobile_confirmed,emailConfirmed:1===i.email_confirmed}}{if("login"===o)return{code:10803,messageValues:{type:"QQ账号"}};const e={qq_openid:{[s]:c},qq_unionid:u};e.my_invite_code=r,e.role=i;const t=await this._registerExec(e);return 0!==t.code?t:{...t,openid:c,unionid:u,...l,mobileConfirmed:!1,emailConfirmed:!1}}},loginByUniverify:async function({openid:e,access_token:t,password:r,inviteCode:n,myInviteCode:i,type:o,needPermission:s,role:a}){const c=this._getConfig(),u=c&&c.service&&c.service.univerify;if(!u)throw new Error("请在config.json中配置service.univerify下一键登录相关参数");if(c.forceInviteCode&&!o)throw new Error("[loginByUniverify] 强制使用邀请码注册时,需指明type为register还是login");const d=await Jr({...u,openid:e,access_token:t});if(0!==d.code)return d;const l=String(d.phoneNumber);let p=await M.where({mobile:l}).get();if(p=this._getCurrentAppUser(p.data),p&&p.length>0){if("register"===o)return{code:10601,messageValues:{type:"手机号"}};const e=p[0],t=await this._loginExec(e,{needPermission:s});return 0!==t.code?t:{...t,mobile:l}}if("login"===o)return{code:10602,messageValues:{type:"手机号"}};const f=Date.now(),m={mobile:l,my_invite_code:i,mobile_confirmed:1,role:a},h=r&&r.trim();if(h){const{passwordHash:e,version:t}=this.encryptPwd(h);m.password=e,t&&(m.password_secret_version=t)}if(n){let e=await M.where({my_invite_code:n}).get();if(1!==e.data.length)return{code:10203,msg:"邀请码无效"};e=e.data[0],m.inviter_uid=[e._id].concat(e.inviter_uid||[]),m.invite_time=f}else if(c.forceInviteCode)return{code:10203,msg:"邀请码无效"};m.my_invite_code=i;const g=await this._registerExec(m,{needPermission:s});return 0!==g.code?g:{...g,mobile:l}},loginByApple:async function({nickName:e,fullName:t,identityToken:r,myInviteCode:n,type:i,needPermission:o,platform:s,role:a}){const c=this._getConfig(),u=c&&c["app-plus"]&&c["app-plus"].oauth&&c["app-plus"].oauth.apple;if(!u)throw new Error("请在config.json或init方法中,app-plus.oauth.apple 下配置相关参数");const{bundleId:d}=u;if(!d)throw new Error("请在config.json或init方法中 app-plus.oauth.apple 下配置bundleId");if(!r)throw new Error("[loginByApple] 苹果登录需要传递identityToken");const l=s||this.context.PLATFORM;t=e||(t&&Object.keys(t).length>0?t.familyName+t.givenName:"");const{code:p,msg:f}=await qr({clientType:l}).verifyIdentityToken(r);if(0!==p)return{code:p,msg:f};const{iss:m,sub:h,aud:g,email:y}=f;if("https://appleid.apple.com"!==m)return{code:10706,msg:"签发机构检验失败",messageValues:{account:"苹果账号"}};if(!h)return{code:10701,msg:"获取用户唯一标识符失败",messageValues:{account:"苹果账号"}};if(d!==g)return{code:10702,msg:"bundleId校验失败,请确认配置后重试",messageValues:{account:"苹果账号"}};const w=t||"新用户"+y.split("@")[0];let _=await M.where({apple_openid:h}).get();if(_=this._getCurrentAppUser(_.data),_&&_.length>0){if("register"===i)return{code:10703,messageValues:{type:"苹果账号"}};const e=_[0],t=await this._loginExec(e,{needPermission:o});return 0!==t.code?t:{...t,openid:h}}if("login"===i)return{code:10704,messageValues:{type:"苹果账号"}};const v={nickname:w,apple_openid:h,my_invite_code:n,role:a},b=await this._registerExec(v,{needPermission:o});return 0!==b.code?b:{...b,openid:h}},login:async function({username:e,password:t,queryField:r=[],needPermission:n}){const i=zr.command,o=[];r&&r.length||(r=["username"]),r.length>1&&console.warn("检测到当前使用queryField匹配多字段进行登录操作,需要注意:uni-id并未限制用户名不能是手机号或邮箱,需要开发者自行限制。否则可能出现用户输入abc@xx.com会同时匹配到邮箱为此值的用户和用户名为此值的用户,导致登录失败");const{usernameToLowerCase:s,emailToLowerCase:a,passwordErrorLimit:c,passwordErrorRetryTime:u}=this._getConfig(),d={email:{email_confirmed:1},mobile:{mobile_confirmed:1}},l={},p=e.trim();if(!p)return{code:F,messageValues:{param:"用户名"}};s&&(l.username=p.toLowerCase()),a&&(l.email=p.toLowerCase());const f=[];r.forEach(t=>{o.push({[t]:e,...d[t]});const r={field:t,value:e};"username"===t&&l.username!==e?(o.push({[t]:l.username,...d[t]}),r.fallbackValue=l.username):"email"===t&&l.email!==e&&(o.push({[t]:l.email,...d[t]}),r.fallbackValue=l.email),f.push(r)});let m=await M.where(i.or(...o)).limit(1).get();m=this._getCurrentAppUser(m.data);const h=this.context.CLIENTIP,g=this._getMatchedUser(m,f);if(g.code)return g;const{userMatched:y}=g;let w=y.login_ip_limit||[];w=w.filter(e=>e.last_error_time>Date.now()-1e3*u);let _=w.find(e=>e.ip===h);if(_&&_.error_times>=c)return{code:10103,msg:`密码错误次数过多,请${N(_.last_error_time+1e3*u)}再试。`};const v=t&&t.trim();if(!v)return{code:F,messageValues:{param:"密码"}};const b=this._checkPwd(y,v);if(0===b.code){const e=w.indexOf(_);e>-1&&w.splice(e,1);const t={login_ip_limit:w},{passwordHash:r,passwordVersion:i}=b;r&&i&&(t.password=r,t.password_secret_version=i);const o=await this._loginExec(y,{needPermission:n,extraData:t});return o.code,o}return _?(_.error_times++,_.last_error_time=Date.now()):(_={ip:h,error_times:1,last_error_time:Date.now()},w.push(_)),await M.doc(y._id).update({login_ip_limit:w}),{code:10102,msg:"密码错误"}},register:async function(e){const t=[],r=[{name:"username",desc:"用户名"},{name:"email",desc:"邮箱",extraCond:{email_confirmed:1}},{name:"mobile",desc:"手机号",extraCond:{mobile_confirmed:1}}],{usernameToLowerCase:n,emailToLowerCase:i}=this._getConfig();r.forEach(r=>{const o=r.name;let s=e[o]&&e[o].trim();s?(("username"===r.name&&n||"email"===r.name&&i)&&(s=s.toLowerCase()),e[o]=s,t.push({[o]:s,...r.extraCond})):delete e[o]});const{username:o,email:s,mobile:a,myInviteCode:c,needPermission:u,autoSetDcloudAppid:d=!0}=e;if("needPermission"in e&&delete e.needPermission,"autoSetDcloudAppid"in e&&delete e.autoSetDcloudAppid,0===t.length)return{code:20101,messageValues:{param:"用户名、邮箱或手机号"}};const l=Wr.command;let p=await M.where(l.or(...t)).get();if(p=this._getCurrentAppUser(p.data),p&&p.length>0){const t=p[0];for(let n=0;nt[e]===i.extraCond[e])),t[i.name]===e[i.name]&&o)return{code:20102,messageValues:{type:i.desc}}}}const f=e.password&&e.password.trim();if(!f)return{code:F,messageValues:{param:"密码"}};const{passwordHash:m,version:h}=this.encryptPwd(f);e.password=m,h&&(e.password_secret_version=h),e.my_invite_code=c,delete e.myInviteCode;const g=await this._registerExec(e,{needPermission:u,autoSetDcloudAppid:d});return 0!==g.code?g:{...g,username:o,email:s,mobile:a}},logout:async function(e){const t=await this.checkToken(e);if(t.code)return t;const r=Xr.command;return await M.doc(t.uid).update({token:r.pull(e)}),{code:0,msg:"退出成功"}},getRoleByUid:async function({uid:e}){if(!e)return{code:F,messageValues:{param:"用户Id"}};const t=await M.doc(e).get();return 0===t.data.length?{code:H}:{code:0,msg:"获取角色成功",role:t.data[0].role||[]}},getPermissionByRole:async function({roleID:e}){if(!e)return{code:F,messageValues:{param:"角色ID"}};if("admin"===e){return{code:0,msg:"获取权限成功",permission:(await q.limit(1e3).get()).data.map(e=>e.permission_id)}}const t=await B.where({role_id:e}).get();return 0===t.data.length?{code:G}:{code:0,msg:"获取权限成功",permission:t.data[0].permission||[]}},getPermissionByUid:async function({uid:e}){const t=await M.aggregate().match({_id:e}).project({role:!0}).unwind("$role").lookup({from:"uni-id-roles",localField:"role",foreignField:"role_id",as:"roleDetail"}).unwind("$roleDetail").replaceRoot({newRoot:"$roleDetail"}).end(),r=[];return t.data.forEach(e=>{Array.prototype.push.apply(r,e.permission)}),{code:0,msg:"获取权限成功",permission:x(r)}},bindRole:async function({uid:e,roleList:t,reset:r=!1}){const n={};return"string"==typeof t&&(t=[t]),n.role=r?t:Zr.push(t),await M.doc(e).update(n),{code:0,msg:"角色绑定成功"}},bindPermission:async function({roleID:e,permissionList:t,reset:r=!1}){const n={};return"string"==typeof t&&(t=[t]),n.permission=r?t:Zr.push(t),await B.where({role_id:e}).update(n),{code:0,msg:"权限绑定成功"}},unbindRole:async function({uid:e,roleList:t}){return"string"==typeof t&&(t=[t]),await M.doc(e).update({role:Zr.pull(Zr.in(t))}),{code:0,msg:"角色解绑成功"}},unbindPermission:async function({roleID:e,permissionList:t}){return"string"==typeof t&&(t=[t]),await B.where({role_id:e}).update({permission:Zr.pull(Zr.in(t))}),{code:0,msg:"权限解绑成功"}},addRole:async function({roleID:e,roleName:t,comment:r,permission:n=[]}){return e?"admin"===e?{code:K,messageValues:{param:"roleID",reason:"不可新增roleID为admin的角色"}}:(await B.add({role_id:e,role_name:t,comment:r,permission:n,create_date:Date.now()}),{code:0,msg:"角色新增成功"}):{code:F,messageValues:{param:"角色Id"}}},addPermission:async function({permissionID:e,permissionName:t,comment:r}){return e?(await q.add({permission_id:e,permission_name:t,comment:r,create_date:Date.now()}),{code:0,msg:"权限新增成功"}):{code:F,messageValues:{param:"权限ID"}}},getRoleList:async function({limit:e=20,offset:t=0,needTotal:r=!0}){const n={code:0,msg:"获取角色列表成功",roleList:(await B.skip(t).limit(e).get()).data};if(r){const{total:e}=await B.where({_id:Zr.exists(!0)}).count();n.total=e}return n},getRoleInfo:async function(e){const t=await B.where({role_id:e}).get();return 0===t.data.length?{code:G}:{code:0,...t.data[0]}},updateRole:async function({roleID:e,roleName:t,comment:r,permission:n}){return e?(await B.where({role_id:e}).update({role_name:t,comment:r,permission:n}),{code:0,msg:"角色更新成功"}):{code:F,messageValues:{param:"角色ID"}}},deleteRole:async function({roleID:e}){const t=y(e);if("string"===t)e=[e];else if("array"!==t)throw new Error("roleID只能为字符串或者数组");return await B.where({role_id:Zr.in(e)}).remove(),await M.where({role:Zr.elemMatch(Zr.in(e))}).update({role:Zr.pullAll(e)}),{code:0,msg:"角色删除成功"}},getPermissionList:async function({limit:e=20,offset:t=0,needTotal:r=!0}){const n={code:0,msg:"获取权限列表成功",permissionList:(await q.skip(t).limit(e).get()).data};if(r){const{total:e}=await q.where({_id:Zr.exists(!0)}).count();n.total=e}return n},getPermissionInfo:async function(e){const t=await q.where({permission_id:e}).get();return 0===t.data.length?{code:F,messageValues:{param:"权限ID"}}:{code:0,...t.data[0]}},updatePermission:async function({permissionID:e,permissionName:t,comment:r}){return e?(await q.where({permission_id:e}).update({permission_name:t,comment:r}),{code:0,msg:"权限更新成功"}):{code:F,messageValues:{param:"权限ID"}}},deletePermission:async function({permissionID:e}){const t=y(e);if("string"===t)e=[e];else if("array"!==t)throw new Error("permissionID只能为字符串或者数组");return await q.where({permission_id:Zr.in(e)}).remove(),await B.where({permission:Zr.elemMatch(Zr.in(e))}).update({permission:Zr.pullAll(e)}),{code:0,msg:"权限删除成功"}},bindAlipay:async function({uid:e,code:t,platform:r}){const n=r||this.context.PLATFORM,{openid:i}=await this._getAlipayApi({platform:n}).code2Session(t);if(!i)return{code:60401,messageValues:{account:"支付宝账号"}};let o=await M.where({ali_openid:i}).get();return o=this._getCurrentAppUser(o.data),o&&o.length>0?{code:60402,messageValues:{type:"支付宝账号"}}:(await M.doc(e).update({ali_openid:i}),{code:0,openid:i,msg:"绑定成功"})},bindEmail:async function({uid:e,email:t,code:r}){if(!(t=t&&t.trim()))return{code:F,messageValues:{param:"邮箱"}};if(!r)return{code:F,messageValues:{param:"验证码"}};const{emailToLowerCase:n}=this._getConfig();n&&(t=t.toLowerCase());let i=await M.where({email:t,email_confirmed:1}).get();if(i=this._getCurrentAppUser(i.data),i&&i.length>0)return{code:60201,messageValues:{type:"邮箱"}};if(r){const e=await this.verifyCode({email:t,code:r,type:"bind"});if(0!==e.code)return e}return await M.doc(e).update({email:t,email_confirmed:1}),{code:0,msg:"邮箱绑定成功",email:t}},bindMobile:async function({uid:e,mobile:t,code:r,openid:n,access_token:i,type:o="sms"}){if("univerify"===o){const e=this._getConfig(),r=e&&e.service&&e.service.univerify;if(!r)throw new Error("请在config.json中配置service.univerify下一键登录相关参数");const o=await Jr({...r,openid:n,access_token:i});if(0!==o.code)return o;t=""+o.phoneNumber}let s=await M.where({mobile:t,mobile_confirmed:1}).get();if(s=this._getCurrentAppUser(s.data),s&&s.length>0)return{code:60101,messageValues:{type:"手机号"}};if("sms"===o&&r){if(!t)return{code:F,messageValues:{param:"手机号码"}};if(!r)return{code:F,messageValues:{param:"验证码"}};const e=await this.verifyCode({mobile:t,code:r,type:"bind"});if(0!==e.code)return e}return await M.doc(e).update({mobile:t,mobile_confirmed:1}),{code:0,msg:"手机号码绑定成功",mobile:t}},bindWeixin:async function({uid:e,code:t,platform:r}){const n=r||this.context.PLATFORM,i="mp-weixin"===n,{openid:o,unionid:s,sessionKey:a,accessToken:c,refreshToken:u,expired:d}=await this._getWeixinApi({platform:n})[i?"code2Session":"getOauthAccessToken"](t);if(!o)return{code:60301,messageValues:{account:"微信openid"}};const l=en.command,p=[{wx_openid:{[n]:o}}];s&&p.push({wx_unionid:s});let f=await M.where(l.or(...p)).get();if(f=this._getCurrentAppUser(f.data),f&&f.length>0)return{code:60302,messageValues:{type:"微信账号"}};const m={wx_openid:{[n]:o}};let h;return s&&(m.wx_unionid=s),await M.doc(e).update(m),h=i?{sessionKey:a}:{accessToken:c,refreshToken:u,accessTokenExpired:d},{code:0,openid:o,unionid:s,...h,msg:"绑定成功"}},bindQQ:async function({uid:e,code:t,accessToken:r,platform:n}={}){const i=n||this.context.PLATFORM,o="mp-qq"===i,{openid:s,unionid:a,sessionKey:c}=await this._getQQApi()[o?"code2Session":"getOpenidByToken"]({code:t,accessToken:r});if(!s)return{code:60501,messageValues:{account:"qq openid"}};const u=tn.command,d=[{qq_openid:{[i]:s}}];a&&d.push({qq_unionid:a});let l=await M.where(u.or(...d)).get();if(l=this._getCurrentAppUser(l.data),l&&l.length>0)return{code:60502,messageValues:{type:"QQ账号"}};const p={qq_openid:{[i]:s}};return a&&(p.qq_unionid=a),await M.doc(e).update(p),{code:0,openid:s,unionid:a,...{accessToken:r,sessionKey:c},msg:"绑定成功"}},unbindAlipay:async function(e){const t=rn.command,r=await M.doc(e).update({ali_openid:t.remove()});return S("upRes:",r),1===r.updated?{code:0,msg:"支付宝解绑成功"}:{code:70401,msg:"支付宝解绑失败,请稍后再试"}},unbindEmail:async function({uid:e,email:t,code:r}){if(t=t&&t.trim(),!e||!t)return{code:F,messageValues:{param:e?"邮箱":"用户Id"}};const{emailToLowerCase:n}=this._getConfig();if(r){const e=await this.verifyCode({email:t,code:r,type:"unbind"});if(0!==e.code)return e}const i=nn.command;let o={_id:e,email:t};if(n){const r=t.toLowerCase();r!==t&&(o=i.or(o,{_id:e,email:r}))}return 1===(await M.where(o).update({email:i.remove(),email_confirmed:i.remove()})).updated?{code:0,msg:"邮箱解绑成功"}:{code:70201,msg:"邮箱解绑失败,请稍后再试"}},unbindMobile:async function({uid:e,mobile:t,code:r}){if(r){const e=await this.verifyCode({mobile:t,code:r,type:"unbind"});if(0!==e.code)return e}const n=on.command;return 1===(await M.where({_id:e,mobile:t}).update({mobile:n.remove(),mobile_confirmed:n.remove()})).updated?{code:0,msg:"手机号解绑成功"}:{code:70101,msg:"手机号解绑失败,请稍后再试"}},unbindWeixin:async function(e){const t=sn.command,r=await M.doc(e).update({wx_openid:t.remove(),wx_unionid:t.remove()});return S("upRes:",r),1===r.updated?{code:0,msg:"微信解绑成功"}:{code:70301,msg:"微信解绑失败,请稍后再试"}},unbindQQ:async function(e){const t=an.command,r=await M.doc(e).update({qq_openid:t.remove(),qq_unionid:t.remove()});return S("upRes:",r),1===r.updated?{code:0,msg:"QQ解绑成功"}:{code:70501,msg:"QQ解绑失败,请稍后再试"}},code2SessionAlipay:async function(e){let t=e;"string"==typeof e&&(t={code:e});try{const e=t.platform||this.context.PLATFORM,r=await this._getAlipayApi({platform:e}).code2Session(t.code);return r.openid?{code:0,msg:"",...r}:{code:80701,messageValues:{account:"支付宝账号"}}}catch(e){return{code:80702,messageValues:{account:"支付宝账号"}}}},code2SessionWeixin:async function(e){let t=e;"string"==typeof e&&(t={code:e});try{const e=t.platform||this.context.PLATFORM,r=await this._getWeixinApi({platform:e})["mp-weixin"===e?"code2Session":"getOauthAccessToken"](t.code);return r.openid?{code:0,msg:"",...r}:{code:80601,messageValues:{account:"微信openid"}}}catch(e){return{code:80602,messageValues:{account:"微信openid"}}}},verifyAppleIdentityToken:async function({identityToken:e,platform:t}){const r=t||this.context.PLATFORM,{code:n,msg:i}=await qr({clientType:r}).verifyIdentityToken(e);return 0!==n?{code:n,msg:i}:{code:n,msg:"验证通过",...i}},wxBizDataCrypt:async function({code:e,sessionKey:t,encryptedData:r,iv:i}){if(!r)return{code:80805,messageValues:{param:"encryptedData"}};if(!i)return{code:80806,messageValues:{param:"iv"}};if(!e&&!t)return{code:80804,messageValues:{param:"code或sessionKey"}};const o=this._getWeixinApi();if(!t){const r=await o.code2Session(e);if(!r.sessionKey)return{code:80801,msg:"sessionKey获取失败"};t=r.sessionKey}t=Buffer.from(t,"base64"),r=Buffer.from(r,"base64"),i=Buffer.from(i,"base64");try{var s=n.createDecipheriv("aes-128-cbc",t,i);s.setAutoPadding(!0);var a=s.update(r,"binary","utf8");a+=s.final("utf8"),a=JSON.parse(a)}catch(e){return{code:80802,msg:"解密失败:"+e.message}}return a.watermark.appid!==o.options.appId?{code:80803,msg:"appid不匹配"}:{code:0,msg:"解密成功",...a}},encryptPwd:function(e,{value:t,version:r}={}){if(!(e=e&&e.trim()))throw new Error("密码不可为空");if(!t){const e=this._getConfig(),{passwordSecret:n}=e;if("array"===y(n)){const e=n.sort((e,t)=>e.version-t.version);t=e[e.length-1].value,r=e[e.length-1].version}else t=n}if(!t)throw new Error("passwordSecret不正确");const i=n.createHmac("sha1",t.toString("ascii"));return i.update(e),{passwordHash:i.digest("hex"),version:r}},checkToken:async function(e,{needPermission:t,needUserInfo:r=!0}={}){const n=this._getConfig();try{const i=this._verifyToken(e);if(i.code)return i;const{uid:o,needPermission:s,role:a,permission:c,exp:u}=i,d=a&&c;t=void 0===t?s:t;const l=n.removePermissionAndRoleFromToken||!d||r,p=!n.removePermissionAndRoleFromToken&&!d||n.removePermissionAndRoleFromToken&&d||n.tokenExpiresThreshold&&u-Date.now()/1e3-1===r.indexOf(e)),t.push(e.token),await M.doc(o).update({token:t,last_login_date:Date.now(),last_login_ip:this.context.CLIENTIP}),{...m,...e}}return m}catch(e){return{code:90001,msg:"数据库读写异常:"+e.message,err:e}}},createToken:function({uid:e,needPermission:t,role:r,permission:n}){if(!e)return{code:30101,messageValues:{param:"用户ID"}};const i={uid:e,needPermission:t,role:r,permission:n},o=this._getConfig();if(!this.interceptorMap.has("customToken")){const e={...i};return this._createTokenInternal({signContent:e,config:o})}const s=this.interceptorMap.get("customToken");if("function"!=typeof s)throw new Error("custom-token.js应导出一个function");const a=s(i);return a instanceof Promise?a.then(e=>this._createTokenInternal({signContent:e,config:o})):this._createTokenInternal({signContent:a,config:o})},_checkPwd:function(e,t){if(!t)return{code:1,message:"密码不能为空"};const{password:r,password_secret_version:n}=e,i=this._getConfig(),{passwordSecret:o}=i,s=y(o);if("string"===s){const{passwordHash:e}=this.encryptPwd(t,{value:o});return e===r?{code:0,message:"密码校验通过"}:{code:2,message:"密码不正确"}}if("array"!==s)throw new Error("config内passwordSecret类型错误,只可设置string类型和array类型");const a=o.sort((e,t)=>e.version-t.version);let c;if(c=n?a.find(e=>e.version===n):a[0],!c)return{code:3,message:"secretVersion不正确"};const u=a[a.length-1],{passwordHash:d}=this.encryptPwd(t,c);if(d===r){const e={code:0,message:"密码校验通过"};if(c!==u){const{passwordHash:r,version:n}=this.encryptPwd(t,u);e.passwordHash=r,e.passwordVersion=n}return e}return{code:4,message:""}},_verifyToken:function(e){const t=this._getConfig();let r;try{r=jr(e,t.tokenSecret)}catch(e){return"TokenExpiredError"===e.name?{code:30203,msg:"token已过期,请重新登录",err:e}:{code:30204,msg:"非法token",err:e}}return t.bindTokenToDevice&&r.clientId&&r.clientId!==this._getClientUaHash()?{code:30201,msg:"token不合法,请重新登录"}:{code:0,message:"",...r}},_getExpiredToken:function(e){const t=this._getConfig(),r=[];return e.forEach(e=>{try{jr(e,t.tokenSecret)}catch(t){r.push(e)}}),r},_getPermissionListByRoleList:async function(e){if(!Array.isArray(e))return[];if(0===e.length)return[];if(e.includes("admin")){return(await q.limit(500).get()).data.map(e=>e.permission_id)}const t=await B.where({role_id:cn.in(e)}).get(),r=[];return t.data.forEach(e=>{Array.prototype.push.apply(r,e.permission)}),x(r)},_getClientUaHash:function(){const e=n.createHash("md5"),t=/MicroMessenger/i.test(this.context.CLIENTUA)?this.context.CLIENTUA.replace(/(MicroMessenger\S+).*/i,"$1"):this.context.CLIENTUA;return e.update(t),e.digest("hex")},_createTokenInternal:function({signContent:e,config:t}){if(t.tokenExpiresIn&&t.tokenExpiresThreshold&&t.tokenExpiresIn<=t.tokenExpiresThreshold)throw new Error(`tokenExpiresIn(${t.tokenExpiresIn})不可小于或等于tokenExpiresThreshold(${t.tokenExpiresThreshold})`);return"object"===y(e)&&e.uid?(t.bindTokenToDevice&&(e.clientId=this._getClientUaHash()),{token:Nr(e,t.tokenSecret,{expiresIn:t.tokenExpiresIn}),tokenExpired:Date.now()+1e3*t.tokenExpiresIn}):{code:30101,messageValues:{param:"用户ID"}}},setVerifyCode:async function({mobile:e,email:t,code:r,expiresIn:n,type:i}){if(t=t&&t.trim(),e=e&&e.trim(),t){const{emailToLowerCase:e}=this._getConfig();e&&(t=t.toLowerCase())}if(!e&&!t)return{code:50101,messageValues:{param:"手机号或邮箱"}};if(e&&t)return{code:50102,messageValues:{param:"参数",reason:"手机号和邮箱不可同时存在"}};r||(r=I()),n||(n=180);const o=Date.now(),s={mobile:e,email:t,type:i,code:r,state:0,ip:this.context.CLIENTIP,created_at:o,expired_at:o+1e3*n};return S("addRes",await L.add(s)),{code:0,mobile:e,email:t}},verifyCode:async function({mobile:e,email:t,code:r,type:n}){if(t=t&&t.trim(),e=e&&e.trim(),t){const{emailToLowerCase:e}=this._getConfig();e&&(t=t.toLowerCase())}if(!e&&!t)return{code:50201,messageValues:{param:"手机号或邮箱"}};if(e&&t)return{code:50203,messageValues:{param:"参数",reason:"手机号和邮箱不可同时存在"}};const i=un.command,o=Date.now(),s={mobile:e,email:t,type:n,code:r,state:0,expired_at:i.gt(o)},a=await L.where(s).orderBy("created_at","desc").limit(1).get();if(S("verifyRecord:",a),a&&a.data&&a.data.length>0){const e=a.data[0];return S("upRes",await L.doc(e._id).update({state:1})),{code:0,msg:"验证通过"}}return{code:50202,msg:"验证码错误或已失效"}},sendSmsCode:async function({mobile:e,code:t,type:r,templateId:n}){if(!e)throw new Error("手机号码不可为空");if(t||(t=I()),!r)throw new Error("验证码类型不可为空");const i=this._getConfig();let o=i&&i.service&&i.service.sms;if(!o)throw new Error("请在config.json或init方法中配置service.sms下短信相关参数");o=Object.assign({codeExpiresIn:300},o);const s=["smsKey","smsSecret"];if(!n&&!o.name)throw new Error("不传入templateId时应在config.json或init方法内service.sms下配置name字段以正确使用uniID_code模板");for(let e=0,t=s.length;e=0?o:{code:0,msg:"验证码发送成功"}}catch(e){return{code:50301,msg:"验证码发送失败, "+e.message}}}});let ln;try{ln=require("uni-config-center")}catch(e){}const pn="\n传入配置的方式有以下几种:\n- 在uni-config-center公共模块的uni-id目录下放置config.json文件(推荐)\n- 在uni-id公共模块的目录下放置config.json文件\n- 使用init方法传入配置\n- 如果使用uni-config-center且HBuilderX版本低于3.1.8,批量上传云函数及公共模块后需要再单独上传一次uni-id";class fn{constructor({context:e,config:t}={}){const r=ln&&ln({pluginId:"uni-id"});this.pluginConfig=r,this.config=t||this._getConfigContent(),Object.defineProperty(this,"context",{get:()=>e||global.__ctx__}),this.interceptorMap=new Map,r&&r.hasFile("custom-token.js")&&this.setInterceptor("customToken",require(r.resolve("custom-token.js")))}get dev(){return console.warn("当前正在使用uniID.dev属性,注意此属性仅可用于开发调试"),{getConfig:this._getConfig.bind(this)}}_parseConfig(e){return Array.isArray(e)?e:e[0]?Object.values(e):e}_getCurrentAppConfig(e){if(!Array.isArray(e))return e;if(!this.context.APPID)throw new Error("uni-id初始化时未传入DCloud AppId,如果使用云函数url化访问需要使用uniID.createInstance方法创建uni-id实例,并在context内传入APPID参数");return e.find(e=>e.dcloudAppid===this.context.APPID)||e.find(e=>e.isDefaultConfig)}_getConfigContent(){if(this.pluginConfig&&this.pluginConfig.hasFile("config.json")){this._hasConfigFile=!0;try{return this._parseConfig(this.pluginConfig.config())}catch(e){return}}const e=r.resolve(__dirname,"config.json");this._hasConfigFile=t.existsSync(e);try{return this._parseConfig(require(e))}catch(e){}}init(e){console.warn("uniID.init接口已废弃,如需自行传入配置请使用uniID.createInstance接口创建uniID实例来使用"),this.config=e}setInterceptor(e,t){this.interceptorMap.set(e,t)}_getConfig(e){const t=this.config&&0!==Object.keys(this.config).length;if(this._hasConfigFile&&!t)throw new Error("请确保公用模块uni-id对应的配置文件格式正确(不可包含注释)"+pn);if(!t)throw new Error("公用模块uni-id缺少配置信息"+pn);const r=this._getCurrentAppConfig(this.config),n=Object.assign(r,r[e||this.context.PLATFORM])||{},i=Object.assign({bindTokenToDevice:!1,tokenExpiresIn:7200,tokenExpiresThreshold:1200,passwordErrorLimit:6,passwordErrorRetryTime:3600,usernameToLowerCase:!0,emailToLowerCase:!0},n);return["passwordSecret","tokenSecret","tokenExpiresIn","passwordErrorLimit","passwordErrorRetryTime"].forEach(e=>{if(!i||!i[e])throw new Error("请在公用模块uni-id的配置信息中内添加配置项:"+e)}),i}}for(const e in dn)fn.prototype[e]=dn[e];const mn=["wxBizDataCrypt","verifyAppleIdentityToken","code2SessionWeixin","code2SessionAlipay"];function hn({context:e,config:t}={}){const r=new fn({context:e,config:t});return new Proxy(r,{get(e,t){if(t in e&&0!==t.indexOf("_"))return"function"==typeof e[t]?(mn.indexOf(t)>-1&&console.warn(`uniID.${t}方法即将废弃,后续版本将不再暴露此方法`),(r=e[t],function(){const e=r.apply(this,arguments);return g(e)?e.then(e=>(U(e),e)):(U(e),e)}).bind(e)):e[t];var r}})}fn.prototype.createInstance=hn;var gn=hn();module.exports=gn; diff --git a/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/package.json b/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/package.json new file mode 100644 index 0000000..00a527f --- /dev/null +++ b/uni_modules/uni-id/uniCloud/cloudfunctions/common/uni-id/package.json @@ -0,0 +1,16 @@ +{ + "name": "uni-id", + "version": "3.3.6", + "description": "uni-id for uniCloud", + "main": "index.js", + "homepage": "https://uniapp.dcloud.io/uniCloud/uni-id", + "repository": { + "type": "git", + "url": "git+https://gitee.com/dcloud/uni-id.git" + }, + "author": "", + "license": "Apache-2.0", + "dependencies": { + "uni-config-center": "file:../../../../../uni-config-center/uniCloud/cloudfunctions/common/uni-config-center" + } +} \ No newline at end of file diff --git a/uni_modules/uni-image-menu/changelog.md b/uni_modules/uni-image-menu/changelog.md new file mode 100644 index 0000000..e69de29 diff --git a/uni_modules/uni-image-menu/js_sdk/uni-image-menu.js b/uni_modules/uni-image-menu/js_sdk/uni-image-menu.js new file mode 100644 index 0000000..b482b83 --- /dev/null +++ b/uni_modules/uni-image-menu/js_sdk/uni-image-menu.js @@ -0,0 +1,169 @@ +var nvMask,nvImageMenu; +export default { + show({list,cancelText},callback){ + console.log(789789879); + if(!list){ + list = [{ + "img":"/static/sharemenu/wechatfriend.png", + "text":"图标文字" + }] + } + //以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心 + var screenWidth = plus.screen.resolutionWidth + //以360px宽度屏幕为例,上下左右边距及2排按钮边距留25像素,图标宽度55像素,同行图标间的间距在360宽的屏幕是30px,但需要动态计算,以此原则计算4列图标分别的left位置 + //图标下的按钮文字距离图标5像素,文字大小12像素 + //底部取消按钮高度固定为44px + //TODO 未处理横屏和pad,这些情况6个图标应该一排即可 + var margin = 20, + iconWidth = 60, + icontextSpace = 5, + textHeight = 12 + var left1 = margin / 360 * screenWidth + var iconSpace = (screenWidth - (left1 * 2) - (iconWidth * 4)) / 3 //屏幕宽度减去左右留白间距,再减去4个图标的宽度,就是3个同行图标的间距 + if (iconSpace <= 5) { //屏幕过窄时,缩小边距和图标大小,再算一次 + margin = 15 + iconWidth = 40 + left1 = margin / 360 * screenWidth + iconSpace = (screenWidth - (left1 * 2) - (iconWidth * 4)) / 3 //屏幕宽度减去左右留白间距,再减去4个图标的宽度,就是3个同行图标的间距 + } + var left2 = left1 + iconWidth + iconSpace + var left3 = left1 + (iconWidth + iconSpace) * 2 + var left4 = left1 + (iconWidth + iconSpace) * 3 + var top1 = left1 + var top2 = top1 + iconWidth + icontextSpace + textHeight + left1 + + const TOP = {top1,top2}, LEFT = {left1,left2,left3,left4}; + + nvMask = new plus.nativeObj.View("nvMask", { //先创建遮罩层 + top: '0px', + left: '0px', + height: '100%', + width: '100%', + backgroundColor: 'rgba(0,0,0,0.2)' + }); + nvMask.addEventListener("click", function() { //处理遮罩层点击 + nvMask.hide(); + nvImageMenu.hide(); + }) + nvImageMenu = new plus.nativeObj.View("nvImageMenu", { //创建底部图标菜单 + bottom: '0px', + left: '0px', + height: (iconWidth + textHeight + 2 * margin)*Math.ceil(list.length/4) +44+'px',//'264px', + width: '100%', + backgroundColor: 'rgb(255,255,255)' + }); + + let myList = [] + list.forEach((item,i)=>{ + myList.push({ + tag: 'img', + src: item.img, + position: { + top: TOP['top'+( parseInt(i/4) +1)], + left: LEFT['left'+(1+i%4)], + width: iconWidth, + height: iconWidth + } + }) + myList.push({ + tag: 'font', + text: item.text, + textStyles: { + size: textHeight + }, + position: { + top: TOP['top'+(parseInt(i/4)+1)] + iconWidth + icontextSpace, + left: LEFT['left'+(1+i%4)], + width: iconWidth, + height: textHeight + } + }) + }) + + //绘制底部图标菜单的内容 + nvImageMenu.draw([ + { + tag: 'rect',//菜单顶部的分割灰线 + color: '#e7e7e7', + position: { + top: '0px', + height: '1px' + } + }, + { + tag: 'font', + text: cancelText,//底部取消按钮的文字 + textStyles: { + size: '14px' + }, + position: { + bottom: '0px', + height: '44px' + } + }, + { + tag: 'rect',//底部取消按钮的顶部边线 + color: '#e7e7e7', + position: { + bottom: '45px', + height: '1px' + } + }, + ...myList + ]) + nvMask.show() + nvImageMenu.show() //5+应支持从底部向上弹出的动画 + + + + nvImageMenu.addEventListener("click",e=>{ //处理底部图标菜单的点击事件,根据点击位置触发不同的逻辑 + // console.log("click menu"+JSON.stringify(e)); + if (e.screenY > plus.screen.resolutionHeight - 44) { //点击了底部取消按钮 + nvMask.hide(); + nvImageMenu.hide(); + } else if (e.clientX < 5 || e.clientX > screenWidth - 5 || e.clientY < 5) { + //屏幕左右边缘5像素及菜单顶部5像素不处理点击 + } else { //点击了图标按钮 + var iClickIndex = -1 //点击的图标按钮序号,第一个图标按钮的index为0 + var iRow = e.clientY < (top2 - (left1 / 2)) ? 0 : 1 + var iCol = -1 + if (e.clientX < (left2 - (iconSpace / 2))) { + iCol = 0 + } else if (e.clientX < (left3 - (iconSpace / 2))) { + iCol = 1 + } else if (e.clientX < (left4 - (iconSpace / 2))) { + iCol = 2 + } else { + iCol = 3 + } + if (iRow == 0) { + iClickIndex = iCol + } else { + iClickIndex = iCol + 4 + } + // console.log("点击按钮的序号: " + iClickIndex); + // if (iClickIndex >= 0 && iClickIndex <= 5) { //处理具体的点击逻辑,此处也可以自行定义逻辑。如果增减了按钮,此处也需要跟着修改 + // } + callback(iClickIndex) + this.hide() + } + }) + /* nvImageMenu.addEventListener("touchstart", function(e) { + if (e.screenY > (plus.screen.resolutionHeight - 44)) { + //TODO 这里可以处理按下背景变灰的效果 + } + }) + nvImageMenu.addEventListener("touchmove", function(e) { + //TODO 这里可以处理按下背景变灰的效果 + if (e.screenY > plus.screen.resolutionHeight - 44) {} + }) + nvImageMenu.addEventListener("touchend", function(e) { + //TODO 这里可以处理释放背景恢复的效果 + }) + */ + }, + hide(){ + nvMask.hide() + nvImageMenu.hide() + } +} \ No newline at end of file diff --git a/uni_modules/uni-image-menu/package.json b/uni_modules/uni-image-menu/package.json new file mode 100644 index 0000000..5938aa9 --- /dev/null +++ b/uni_modules/uni-image-menu/package.json @@ -0,0 +1,76 @@ +{ + "id": "uni-image-menu", + "displayName": "uni-image-menu", + "version": "1.0.0", + "description": "uni-image-menu", + "keywords": [ + "uni-image-menu" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "JS SDK", + "JS SDK" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "", + "data": "", + "permissions": "" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "u", + "aliyun": "u" + }, + "client": { + "App": { + "app-vue": "u", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "u", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "u", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "u", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-image-menu/readme.md b/uni_modules/uni-image-menu/readme.md new file mode 100644 index 0000000..ecca1b0 --- /dev/null +++ b/uni_modules/uni-image-menu/readme.md @@ -0,0 +1,19 @@ +# uni-image-menu + +使用示例: +``` +import uniImageMenu from 'uni_modules/uni-image-menu/js_sdk/uni-image-menu.js'; +uniImageMenu.show({ + list:[{ + "img": "/static/sharemenu/wechatfriend.png", + "text": "微信好友" + }, + { + "img": "/static/sharemenu/wechatmoments.png", + "text": "微信朋友圈" + }], + cancelText:param.cancelText +}, e => { + console.log(e) +}) +``` \ No newline at end of file diff --git a/uni_modules/uni-indexed-list/changelog.md b/uni_modules/uni-indexed-list/changelog.md new file mode 100644 index 0000000..7af85e4 --- /dev/null +++ b/uni_modules/uni-indexed-list/changelog.md @@ -0,0 +1,12 @@ +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.11(2021-05-12) +- 新增 组件示例地址 +## 1.0.10(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.9(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.8(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持 PC 端 diff --git a/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue b/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue new file mode 100644 index 0000000..7976f68 --- /dev/null +++ b/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue b/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue new file mode 100644 index 0000000..665379c --- /dev/null +++ b/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue @@ -0,0 +1,359 @@ + + + diff --git a/uni_modules/uni-indexed-list/package.json b/uni_modules/uni-indexed-list/package.json new file mode 100644 index 0000000..5e1cddc --- /dev/null +++ b/uni_modules/uni-indexed-list/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-indexed-list", + "displayName": "uni-indexed-list 索引列表", + "version": "1.1.0", + "description": "索引列表组件,右侧带索引的列表,方便快速定位到具体内容,通常用于城市/机场选择等场景", + "keywords": [ + "uni-ui", + "索引列表", + "索引", + "列表" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} diff --git a/uni_modules/uni-indexed-list/readme.md b/uni_modules/uni-indexed-list/readme.md new file mode 100644 index 0000000..4fb6a0a --- /dev/null +++ b/uni_modules/uni-indexed-list/readme.md @@ -0,0 +1,67 @@ + + +## IndexedList 索引列表 +> **组件名:uni-indexed-list** +> 代码块: `uIndexedList` + + +用于展示索引列表。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + +``` + +## API + +### IndexedList Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|options |Object |- |索引列表需要的数据对象 | +|showSelect |Boolean|- | 展示模式,true 为展示默认,false 为选择模式,默认为 false | + +**options 数据格式说明** + +```json +[{ + "letter": "A", + "data": [ + "阿克苏机场", + "阿拉山口机场", + "阿勒泰机场", + "阿里昆莎机场", + "安庆天柱山机场", + "澳门国际机场" + ] +}, { + "letter": "B", + "data": [ + "保山机场", + "包头机场", + "北海福成机场", + "北京南苑机场", + "北京首都国际机场" + ] +}] +``` + +### IndexedList Events + +|事件名 |说明 |返回值 | +|:-: |:-: |:-: | +|click |点击列表事件 ,返回当前选择项的事件对象 |- | + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/indexed-list/indexed-list](https://hellouniapp.dcloud.net.cn/pages/extUI/indexed-list/indexed-list) \ No newline at end of file diff --git a/uni_modules/uni-link/changelog.md b/uni_modules/uni-link/changelog.md new file mode 100644 index 0000000..1d24c16 --- /dev/null +++ b/uni_modules/uni-link/changelog.md @@ -0,0 +1,11 @@ +## 0.0.6(2021-07-30) +- 支持自定义插槽 +## 0.0.5(2021-06-21) +- 新增 download 属性,H5平台下载文件名 +## 0.0.4(2021-05-12) +- 新增 组件示例地址 +## 0.0.3(2021-03-09) +- 新增 href 属性支持 tel:|mailto: + +## 0.0.2(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-link/components/uni-link/uni-link.vue b/uni_modules/uni-link/components/uni-link/uni-link.vue new file mode 100644 index 0000000..d8aaaf8 --- /dev/null +++ b/uni_modules/uni-link/components/uni-link/uni-link.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/uni_modules/uni-link/package.json b/uni_modules/uni-link/package.json new file mode 100644 index 0000000..96f2344 --- /dev/null +++ b/uni_modules/uni-link/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-link", + "displayName": "uni-link 超链接", + "version": "0.0.6", + "description": "uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打", + "keywords": [ + "uni-ui", + "uniui", + "link", + "超链接", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "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" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-link/readme.md b/uni_modules/uni-link/readme.md new file mode 100644 index 0000000..0735374 --- /dev/null +++ b/uni_modules/uni-link/readme.md @@ -0,0 +1,48 @@ + + +## Link 链接 +> **组件名:uni-link** +> 代码块: `uLink` + + +uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打开新网页。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + +``` + +## API + +### Link Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|href |String |- |链接地址 | +|text |String |- |显示文字 | +|download |String |- |H5平台下载文件名 | +|showUnderLine|Boolean|true |是否显示下划线 | +|copyTips |String |已自动复制网址,请在手机浏览器里粘贴该网址 |在小程序端复制链接时的提示语 | +|color |String |#999999 |链接文字颜色 | +|fontSize |String |14 |链接文字大小,单位px | + + +### Link Slots + +|名称|说明| +|:-:|:-:| +|default|自定义内容,回覆盖原有的内容显示| + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/link/link](https://hellouniapp.dcloud.net.cn/pages/extUI/link/link) \ No newline at end of file diff --git a/uni_modules/uni-list/changelog.md b/uni_modules/uni-list/changelog.md new file mode 100644 index 0000000..dd6f01d --- /dev/null +++ b/uni_modules/uni-list/changelog.md @@ -0,0 +1,15 @@ +## 1.1.3(2021-08-30) +- 修复 在vue3中to属性在发行应用的时候报错的bug +## 1.1.2(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.1.1(2021-07-21) +- 修复 与其他组件嵌套使用时,点击失效的Bug +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.17(2021-05-12) +- 新增 组件示例地址 +## 1.0.16(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.0.15(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 uni-list-chat 角标显示不正常的问题 diff --git a/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue b/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue new file mode 100644 index 0000000..e256e4c --- /dev/null +++ b/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss b/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss new file mode 100644 index 0000000..311f8d9 --- /dev/null +++ b/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss @@ -0,0 +1,58 @@ +/** + * 这里是 uni-list 组件内置的常用样式变量 + * 如果需要覆盖样式,这里提供了基本的组件样式变量,您可以尝试修改这里的变量,去完成样式替换,而不用去修改源码 + * + */ + +// 背景色 +$background-color : #fff; +// 分割线颜色 +$divide-line-color : #e5e5e5; + +// 默认头像大小,如需要修改此值,注意同步修改 js 中的值 const avatarWidth = xx ,目前只支持方形头像 +// nvue 页面不支持修改头像大小 +$avatar-width : 45px ; + +// 头像边框 +$avatar-border-radius: 5px; +$avatar-border-color: #eee; +$avatar-border-width: 1px; + +// 标题文字样式 +$title-size : 16px; +$title-color : #3b4144; +$title-weight : normal; + +// 描述文字样式 +$note-size : 12px; +$note-color : #999; +$note-weight : normal; + +// 右侧额外内容默认样式 +$right-text-size : 12px; +$right-text-color : #999; +$right-text-weight : normal; + +// 角标样式 +// nvue 页面不支持修改圆点位置以及大小 +// 角标在左侧时,角标的位置,默认为 0 ,负数左/下移动,正数右/上移动 +$badge-left: 0px; +$badge-top: 0px; + +// 显示圆点时,圆点大小 +$dot-width: 10px; +$dot-height: 10px; + +// 显示角标时,角标大小和字体大小 +$badge-size : 18px; +$badge-font : 12px; +// 显示角标时,角标前景色 +$badge-color : #fff; +// 显示角标时,角标背景色 +$badge-background-color : #ff5a5f; +// 显示角标时,角标左右间距 +$badge-space : 6px; + +// 状态样式 +// 选中颜色 +$hover : #f5f5f5; diff --git a/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue b/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue new file mode 100644 index 0000000..70345af --- /dev/null +++ b/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue @@ -0,0 +1,534 @@ + + + + + diff --git a/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue b/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue new file mode 100644 index 0000000..167222b --- /dev/null +++ b/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue @@ -0,0 +1,461 @@ + + + + + diff --git a/uni_modules/uni-list/components/uni-list/uni-list.vue b/uni_modules/uni-list/components/uni-list/uni-list.vue new file mode 100644 index 0000000..1c85003 --- /dev/null +++ b/uni_modules/uni-list/components/uni-list/uni-list.vue @@ -0,0 +1,106 @@ + + + + diff --git a/uni_modules/uni-list/components/uni-list/uni-refresh.vue b/uni_modules/uni-list/components/uni-list/uni-refresh.vue new file mode 100644 index 0000000..3b4c5a2 --- /dev/null +++ b/uni_modules/uni-list/components/uni-list/uni-refresh.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/uni_modules/uni-list/components/uni-list/uni-refresh.wxs b/uni_modules/uni-list/components/uni-list/uni-refresh.wxs new file mode 100644 index 0000000..818a6b7 --- /dev/null +++ b/uni_modules/uni-list/components/uni-list/uni-refresh.wxs @@ -0,0 +1,87 @@ +var pullDown = { + threshold: 95, + maxHeight: 200, + callRefresh: 'onrefresh', + callPullingDown: 'onpullingdown', + refreshSelector: '.uni-refresh' +}; + +function ready(newValue, oldValue, ownerInstance, instance) { + var state = instance.getState() + state.canPullDown = newValue; + // console.log(newValue); +} + +function touchStart(e, instance) { + var state = instance.getState(); + state.refreshInstance = instance.selectComponent(pullDown.refreshSelector); + state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined); + if (!state.canPullDown) { + return + } + + // console.log("touchStart"); + + state.height = 0; + state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY; + state.refreshInstance.setStyle({ + 'height': 0 + }); + state.refreshInstance.callMethod("onchange", true); +} + +function touchMove(e, ownerInstance) { + var instance = e.instance; + var state = instance.getState(); + if (!state.canPullDown) { + return + } + + var oldHeight = state.height; + var endY = e.touches[0].pageY || e.changedTouches[0].pageY; + var height = endY - state.touchStartY; + if (height > pullDown.maxHeight) { + return; + } + + var refreshInstance = state.refreshInstance; + refreshInstance.setStyle({ + 'height': height + 'px' + }); + + height = height < pullDown.maxHeight ? height : pullDown.maxHeight; + state.height = height; + refreshInstance.callMethod(pullDown.callPullingDown, { + height: height + }); +} + +function touchEnd(e, ownerInstance) { + var state = e.instance.getState(); + if (!state.canPullDown) { + return + } + + state.refreshInstance.callMethod("onchange", false); + + var refreshInstance = state.refreshInstance; + if (state.height > pullDown.threshold) { + refreshInstance.callMethod(pullDown.callRefresh); + return; + } + + refreshInstance.setStyle({ + 'height': 0 + }); +} + +function propObserver(newValue, oldValue, instance) { + pullDown = newValue; +} + +module.exports = { + touchmove: touchMove, + touchstart: touchStart, + touchend: touchEnd, + propObserver: propObserver +} diff --git a/uni_modules/uni-list/package.json b/uni_modules/uni-list/package.json new file mode 100644 index 0000000..f7f7ef9 --- /dev/null +++ b/uni_modules/uni-list/package.json @@ -0,0 +1,91 @@ +{ + "id": "uni-list", + "displayName": "uni-list 列表", + "version": "1.1.3", + "description": "List 组件 ,帮助使用者快速构建列表。", + "keywords": [ + "", + "uni-ui", + "uniui", + "列表", + "", + "list" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-badge", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-list/readme.md b/uni_modules/uni-list/readme.md new file mode 100644 index 0000000..e4a5d73 --- /dev/null +++ b/uni_modules/uni-list/readme.md @@ -0,0 +1,347 @@ + + +## List 列表 +> **组件名:uni-list** +> 代码块: `uList`、`uListItem` +> 关联组件:`uni-list-item`、`uni-badge`、`uni-icons`、`uni-list-chat`、`uni-list-ad` + + +List 列表组件,包含基本列表样式、可扩展插槽机制、长列表性能优化、多端兼容。 + +在vue页面里,它默认使用页面级滚动。在app-nvue页面里,它默认使用原生list组件滚动。这样的长列表,在滚动出屏幕外后,系统会回收不可见区域的渲染内存资源,不会造成滚动越长手机越卡的问题。 + +uni-list组件是父容器,里面的核心是uni-list-item子组件,它代表列表中的一个可重复行,子组件可以无限循环。 + +uni-list-item有很多风格,uni-list-item组件通过内置的属性,满足一些常用的场景。当内置属性不满足需求时,可以通过扩展插槽来自定义列表内容。 + +内置属性可以覆盖的场景包括:导航列表、设置列表、小图标列表、通信录列表、聊天记录列表。 + +涉及很多大图或丰富内容的列表,比如类今日头条的新闻列表、类淘宝的电商列表,需要通过扩展插槽实现。 + +下文均有样例给出。 + +uni-list不包含下拉刷新和上拉翻页。上拉翻页另见组件:[uni-load-more](https://ext.dcloud.net.cn/plugin?id=29) + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - 组件内部依赖 `'uni-icons'` 、`uni-badge` 组件 +> - `uni-list` 和 `uni-list-item` 需要配套使用,暂不支持单独使用 `uni-list-item` +> - 只有开启点击反馈后,会有点击选中效果 +> - 使用插槽时,可以完全自定义内容 +> - note 、rightText 属性暂时没做限制,不支持文字溢出隐藏,使用时应该控制长度显示或通过默认插槽自行扩展 +> - 支付宝小程序平台需要在支付宝小程序开发者工具里开启 component2 编译模式,开启方式: 详情 --> 项目配置 --> 启用 component2 编译 +> - 如果需要修改 `switch`、`badge` 样式,请使用插槽自定义 +> - 在 `HBuilderX` 低版本中,可能会出现组件显示 `undefined` 的问题,请升级最新的 `HBuilderX` 或者 `cli` +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + +### 基本用法 + +- 设置 `title` 属性,可以显示列表标题 +- 设置 `disabled` 属性,可以禁用当前项 + +```html + + + + + +``` + +### 多行内容显示 + +- 设置 `note` 属性 ,可以在第二行显示描述文本信息 + +```html + + + + + +``` + +### 右侧显示角标、switch + +- 设置 `show-badge` 属性 ,可以显示角标内容 +- 设置 `show-switch` 属性,可以显示 switch 开关 + +```html + + + + + +``` + +### 左侧显示略缩图、图标 + +- 设置 `thumb` 属性 ,可以在列表左侧显示略缩图 +- 设置 `show-extra-icon` 属性,并指定 `extra-icon` 可以在左侧显示图标 + +```html + + + + +``` + +### 开启点击反馈和右侧箭头 +- 设置 `clickable` 为 `true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件 +- 设置 `link` 属性,会自动开启点击反馈,并给列表右侧添加一个箭头 +- 设置 `to` 属性,可以跳转页面,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo` + +```html + + + + + + + +``` + + +### 聊天列表示例 +- 设置 `clickable` 为 `true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件 +- 设置 `link` 属性,会自动开启点击反馈,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo` +- 设置 `to` 属性,可以跳转页面 +- `time` 属性,通常会设置成时间显示,但是这个属性不仅仅可以设置时间,你可以传入任何文本,注意文本长度可能会影响显示 +- `avatar` 和 `avatarList` 属性同时只会有一个生效,同时设置的话,`avatarList` 属性的长度大于1 ,`avatar` 属性将失效 +- 可以通过默认插槽自定义列表右侧内容 + +```html + + + + + + + + + + + + + + + + + 刚刚 + + + + + + + +``` + +```javascript + +export default { + components: {}, + data() { + return { + avatarList: [{ + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }, { + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }, { + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }] + } + } +} + +``` + + +```css + +.chat-custom-right { + flex: 1; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: space-between; + align-items: flex-end; +} + +.chat-custom-text { + font-size: 12px; + color: #999; +} + +``` + +## API + +### List Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +border |Boolean |true | 是否显示边框 + + +### ListItem Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +title |String |- | 标题 +note |String |- | 描述 +ellipsis |Number |0 | title 是否溢出隐藏,可选值,0:默认; 1:显示一行; 2:显示两行;【nvue 暂不支持】 +thumb |String |- | 左侧缩略图,若thumb有值,则不会显示扩展图标 +thumbSize |String |medium | 略缩图尺寸,可选值,lg:大图; medium:一般; sm:小图; +showBadge |Boolean |false | 是否显示数字角标 +badgeText |String |- | 数字角标内容 +badgeType |String |- | 数字角标类型,参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21) +rightText |String |- | 右侧文字内容 +disabled |Boolean |false | 是否禁用 +showArrow |Boolean |true | 是否显示箭头图标 +link |String |navigateTo | 新页面跳转方式,可选值见下表 +to |String |- | 新页面跳转地址,如填写此属性,click 会返回页面是否跳转成功 +clickable |Boolean |false | 是否开启点击反馈 +showSwitch |Boolean |false | 是否显示Switch +switchChecked |Boolean |false | Switch是否被选中 +showExtraIcon |Boolean |false | 左侧是否显示扩展图标 +extraIcon |Object |- | 扩展图标参数,格式为 ``{color: '#4cd964',size: '22',type: 'spinner'}``,参考 [uni-icons](https://ext.dcloud.net.cn/plugin?id=28) +direction | String |row | 排版方向,可选值,row:水平排列; column:垂直排列; 3个插槽是水平排还是垂直排,也受此属性控制 + + +#### Link Options + +属性名 | 说明 +:-: | :-: +navigateTo | 同 uni.navigateTo() +redirectTo | 同 uni.reLaunch() +reLaunch | 同 uni.reLaunch() +switchTab | 同 uni.switchTab() + +### ListItem Events + +事件称名 |说明 |返回参数 +:-: |:-: |:-: +click |点击 uniListItem 触发事件,需开启点击反馈 |- +switchChange |点击切换 Switch 时触发,需显示 switch |e={value:checked} + + + +### ListItem Slots + +名称 | 说明 +:-: | :-: +header | 左/上内容插槽,可完全自定义默认显示 +body | 中间内容插槽,可完全自定义中间内容 +footer | 右/下内容插槽,可完全自定义右侧内容 + + +> **通过插槽扩展** +> 需要注意的是当使用插槽时,内置样式将会失效,只保留排版样式,此时的样式需要开发者自己实现 +> 如果 `uni-list-item` 组件内置属性样式无法满足需求,可以使用插槽来自定义uni-list-item里的内容。 +> uni-list-item提供了3个可扩展的插槽:`header`、`body`、`footer` +> - 当 `direction` 属性为 `row` 时表示水平排列,此时 `header` 表示列表的左边部分,`body` 表示列表的中间部分,`footer` 表示列表的右边部分 +> - 当 `direction` 属性为 `column` 时表示垂直排列,此时 `header` 表示列表的上边部分,`body` 表示列表的中间部分,`footer` 表示列表的下边部分 +> 开发者可以只用1个插槽,也可以3个一起使用。在插槽中可自主编写view标签,实现自己所需的效果。 + + +**示例** + +```html + + + + + + + + + 自定义插槽 + + + + +``` + + + + + +### ListItemChat Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +title |String |- | 标题 +note |String |- | 描述 +clickable |Boolean |false | 是否开启点击反馈 +badgeText |String |- | 数字角标内容,设置为 `dot` 将显示圆点 +badgePositon |String |right | 角标位置 +link |String |navigateTo | 是否展示右侧箭头并开启点击反馈,可选值见下表 +clickable |Boolean |false | 是否开启点击反馈 +to |String |- | 跳转页面地址,如填写此属性,click 会返回页面是否跳转成功 +time |String |- | 右侧时间显示 +avatarCircle |Boolean |false | 是否显示圆形头像 +avatar |String |- | 头像地址,avatarCircle 不填时生效 +avatarList |Array |- | 头像组,格式为 [{url:''}] + +#### Link Options + +属性名 | 说明 +:-: | :-: +navigateTo | 同 uni.navigateTo() +redirectTo | 同 uni.reLaunch() +reLaunch | 同 uni.reLaunch() +switchTab | 同 uni.switchTab() + +### ListItemChat Slots + +名称 | 说明 +:- | :- +default | 自定义列表右侧内容(包括时间和角标显示) + +### ListItemChat Events +事件称名 | 说明 | 返回参数 +:-: | :-: | :-: +@click | 点击 uniListChat 触发事件 | {data:{}} ,如有 to 属性,会返回页面跳转信息 + + + + + + +## 基于uni-list扩展的页面模板 + +通过扩展插槽,可实现多种常见样式的列表 + +**新闻列表类** + +1. 云端一体混合布局:[https://ext.dcloud.net.cn/plugin?id=2546](https://ext.dcloud.net.cn/plugin?id=2546) +2. 云端一体垂直布局,大图模式:[https://ext.dcloud.net.cn/plugin?id=2583](https://ext.dcloud.net.cn/plugin?id=2583) +3. 云端一体垂直布局,多行图文混排:[https://ext.dcloud.net.cn/plugin?id=2584](https://ext.dcloud.net.cn/plugin?id=2584) +4. 云端一体垂直布局,多图模式:[https://ext.dcloud.net.cn/plugin?id=2585](https://ext.dcloud.net.cn/plugin?id=2585) +5. 云端一体水平布局,左图右文:[https://ext.dcloud.net.cn/plugin?id=2586](https://ext.dcloud.net.cn/plugin?id=2586) +6. 云端一体水平布局,左文右图:[https://ext.dcloud.net.cn/plugin?id=2587](https://ext.dcloud.net.cn/plugin?id=2587) +7. 云端一体垂直布局,无图模式,主标题+副标题:[https://ext.dcloud.net.cn/plugin?id=2588](https://ext.dcloud.net.cn/plugin?id=2588) + +**商品列表类** + +1. 云端一体列表/宫格视图互切:[https://ext.dcloud.net.cn/plugin?id=2651](https://ext.dcloud.net.cn/plugin?id=2651) +2. 云端一体列表(宫格模式):[https://ext.dcloud.net.cn/plugin?id=2671](https://ext.dcloud.net.cn/plugin?id=2671) +3. 云端一体列表(列表模式):[https://ext.dcloud.net.cn/plugin?id=2672](https://ext.dcloud.net.cn/plugin?id=2672) + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/list/list](https://hellouniapp.dcloud.net.cn/pages/extUI/list/list) \ No newline at end of file diff --git a/uni_modules/uni-load-more/changelog.md b/uni_modules/uni-load-more/changelog.md new file mode 100644 index 0000000..5b60375 --- /dev/null +++ b/uni_modules/uni-load-more/changelog.md @@ -0,0 +1,10 @@ +## 1.2.1(2021-08-24) +- 新增 支持国际化 +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.8(2021-05-12) +- 新增 组件示例地址 +## 1.1.7(2021-03-30) +- 修复 uni-load-more 在首页使用时,h5 平台报 'uni is not defined' 的 bug +## 1.1.6(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json b/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json new file mode 100644 index 0000000..a4f14a5 --- /dev/null +++ b/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "Pull up to show more", + "uni-load-more.contentrefresh": "loading...", + "uni-load-more.contentnomore": "No more data" +} diff --git a/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js b/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json b/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json new file mode 100644 index 0000000..f15d510 --- /dev/null +++ b/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "上拉显示更多", + "uni-load-more.contentrefresh": "正在加载...", + "uni-load-more.contentnomore": "没有更多数据了" +} diff --git a/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json b/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json new file mode 100644 index 0000000..a255c6d --- /dev/null +++ b/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "上拉顯示更多", + "uni-load-more.contentrefresh": "正在加載...", + "uni-load-more.contentnomore": "沒有更多數據了" +} diff --git a/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue b/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue new file mode 100644 index 0000000..19fbc95 --- /dev/null +++ b/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue @@ -0,0 +1,378 @@ + + + + + diff --git a/uni_modules/uni-load-more/package.json b/uni_modules/uni-load-more/package.json new file mode 100644 index 0000000..062ee5d --- /dev/null +++ b/uni_modules/uni-load-more/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-load-more", + "displayName": "uni-load-more 加载更多", + "version": "1.2.1", + "description": "LoadMore 组件,常用在列表里面,做滚动加载使用。", + "keywords": [ + "uni-ui", + "uniui", + "加载更多", + "load-more" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-load-more/readme.md b/uni_modules/uni-load-more/readme.md new file mode 100644 index 0000000..b781faf --- /dev/null +++ b/uni_modules/uni-load-more/readme.md @@ -0,0 +1,70 @@ + + +### LoadMore 加载更多 +> **组件名:uni-load-more** +> 代码块: `uLoadMore` + + +用于列表中,做滚动加载使用,展示 loading 的各种状态。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 使用方式 + +在 ``template`` 中使用组件 + +```html + +``` + +## API + +### LoadMore Props + +|属性名 |类型 | 可选值 |默认值 |说明 | +|:-: |:-: |:-: |:-: |:-: | +|iconSize |Number |- |24 |指定图标大小 | +|status |String |more/loading/noMore |more |loading 的状态 | +|showIcon |Boolean|- |true |是否显示 loading 图标 | +|iconType |String |snow/circle/auto |auto |指定图标样式| +|color |String |- |#777777 |图标和文字颜色 | +|contentText|Object|- |{contentdown: "上拉显示更多",contentrefresh: "正在加载...",contentnomore: "没有更多数据了"}|各状态文字说明 | + + +#### Status Options +|参数名称 |说明 | +|:-: |:-: | +|more |loading前 | +|loading|loading前中 | +|more |没有更多数据 | + +#### IconType Options +|参数名称 |说明 | +|:-: |:-: | +|snow |ios雪花加载样式 | +|circle |安卓环形加载样式 | +|auto |根据平台自动选择加载样式 | + + + + +> **说明** +> `iconType`为`snow`时,在`APP-NVUE`平台不可设置大小,在非`APP-NVUE`平台不可设置颜色 + + + +### 事件说明 + +|事件名 |说明 |返回值 | +|:-: |:-: |:-: | +|clickLoadMore |点击加载更多时触发 |e.detail={status:'loading'}| + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/load-more/load-more](https://hellouniapp.dcloud.net.cn/pages/extUI/load-more/load-more) \ No newline at end of file diff --git a/uni_modules/uni-nav-bar/changelog.md b/uni_modules/uni-nav-bar/changelog.md new file mode 100644 index 0000000..9aaae1f --- /dev/null +++ b/uni_modules/uni-nav-bar/changelog.md @@ -0,0 +1,19 @@ +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.11(2021-05-12) +- 新增 组件示例地址 +## 1.0.10(2021-04-30) +- 修复 在nvue下fixed为true,宽度不能撑满的Bug +## 1.0.9(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.8(2021-04-14) +- uni-ui 修复 uni-nav-bar 当 fixed 属性为 true 时铺不满屏幕的 bug + +## 1.0.7(2021-02-25) +- 修复 easycom 下,找不到 uni-status-bar 的bug + +## 1.0.6(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue b/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue new file mode 100644 index 0000000..33b8430 --- /dev/null +++ b/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue b/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue new file mode 100644 index 0000000..976af6c --- /dev/null +++ b/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/uni_modules/uni-nav-bar/package.json b/uni_modules/uni-nav-bar/package.json new file mode 100644 index 0000000..3b0f20e --- /dev/null +++ b/uni_modules/uni-nav-bar/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-nav-bar", + "displayName": "uni-nav-bar 自定义导航栏", + "version": "1.1.0", + "description": "自定义导航栏组件,主要用于头部导航。", + "keywords": [ + "uni-ui", + "导航", + "导航栏", + "自定义导航栏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-nav-bar/readme.md b/uni_modules/uni-nav-bar/readme.md new file mode 100644 index 0000000..fa34c8e --- /dev/null +++ b/uni_modules/uni-nav-bar/readme.md @@ -0,0 +1,71 @@ + + +### NavBar 导航栏 +*已经支持在nvue页面中使用* + +导航栏组件,主要用于头部导航,组件名:``uni-nav-bar``,代码块: uNavBar。 + +### 使用方式 + +在 ``script`` 中引用组件 + +```javascript +import uniNavBar from '@/components/uni-nav-bar/uni-nav-bar.vue' +export default { + components: {uniNavBar} +} +``` + +在 ``template`` 中使用组件 + +```html + +``` + +### 属性说明 + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|title |String |- |标题文字 | +|leftText |String |- |左侧按钮文本 | +|rightText |String |- |右侧按钮文本 | +|leftIcon |String |- |左侧按钮图标(图标类型参考 [Icon 图标](http://ext.dcloud.net.cn/plugin?id=28) type 属性) | +|rightIcon |String |- |右侧按钮图标(图标类型参考 [Icon 图标](http://ext.dcloud.net.cn/plugin?id=28) type 属性) | +|color |String |#000000|图标和文字颜色 | +|backgroundColor |String |#FFFFFF|导航栏背景颜色 | +|fixed |Boolean|false |是否固定顶部 | +|statusBar |Boolean|false |是否包含状态栏 | +|shadow |Boolean|false |导航栏下是否有阴影 | + +### 插槽说明 + +开发者使用 NavBar 时,支持向 NavBar 里插入不同内容,以达到自定义的目的。 + +|slot名 |说明 | +|:-: |:-: | +|left |向导航栏左侧插入 | +|right |向导航栏右侧插入 | +|default|向导航栏中间插入 | + +```html + + 标题栏 + left + right + +``` + +### 事件说明 + +|事件名 |说明 |返回值 | +|:-: |:-: |:-: | +|@clickLeft |左侧按钮点击时触发 |- | +|@clickRight |右侧按钮点击时触发 |- | + +### 插件预览地址 + +[https://uniapp.dcloud.io/h5/pages/extUI/nav-bar/nav-bar](https://uniapp.dcloud.io/h5/pages/extUI/nav-bar/nav-bar) + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/nav-bar/nav-bar](https://hellouniapp.dcloud.net.cn/pages/extUI/nav-bar/nav-bar) \ No newline at end of file diff --git a/uni_modules/uni-notice-bar/changelog.md b/uni_modules/uni-notice-bar/changelog.md new file mode 100644 index 0000000..348245e --- /dev/null +++ b/uni_modules/uni-notice-bar/changelog.md @@ -0,0 +1,11 @@ +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.9(2021-05-12) +- 新增 组件示例地址 +## 1.0.8(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.7(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.6(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue b/uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue new file mode 100644 index 0000000..878ba7e --- /dev/null +++ b/uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue @@ -0,0 +1,398 @@ + + + + + diff --git a/uni_modules/uni-notice-bar/package.json b/uni_modules/uni-notice-bar/package.json new file mode 100644 index 0000000..6b2e26e --- /dev/null +++ b/uni_modules/uni-notice-bar/package.json @@ -0,0 +1,85 @@ +{ + "id": "uni-notice-bar", + "displayName": "uni-notice-bar 通告栏", + "version": "1.1.0", + "description": "NoticeBar 通告栏组件,常用于展示公告信息,可设为滚动公告", + "keywords": [ + "uni-ui", + "uniui", + "通告栏", + "公告", + "跑马灯" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} diff --git a/uni_modules/uni-notice-bar/readme.md b/uni_modules/uni-notice-bar/readme.md new file mode 100644 index 0000000..f65c578 --- /dev/null +++ b/uni_modules/uni-notice-bar/readme.md @@ -0,0 +1,71 @@ + + +## NoticeBar 通告栏 +> **组件名:uni-notice-bar** +> 代码块: `uNoticeBar` + + +通告栏组件 。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + + + + + + + + + + + + + + +``` + +> **注意** +> 如果需要异步获取内容后展示需要使用`v-if`进行控制,`` + + +## NoticeBar API + +### NoticeBar Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|speed |Number |100 |文字滚动的速度,默认100px/秒 | +|text |String |- |显示文字 | +|background-color |String |#fffbe8|背景颜色 | +|color |String |#de8c17|文字颜色 | +|moreColor |String |#999999|查看更多文字的颜色 | +|moreText |String |- |设置“查看更多”的文本 | +|single |Boolean|false |是否单行 | +|scrollable |Boolean|false |是否滚动,为true时,NoticeBar为单行 | +|showIcon |Boolean|false |是否显示左侧喇叭图标 | +|showClose |Boolean|false |是否显示左侧关闭按钮 | +|showGetMore |Boolean|false |是否显示右侧查看更多图标,为true时,NoticeBar为单行| + +### NoticeBar Events + +|事件名称 |说明 |返回值 | +|:-: |:-: |:-: | +|@click |点击 NoticeBar 触发事件 |- | +|@close |关闭 NoticeBar 触发事件 |- | +|@getmore |点击”查看更多“时触发事件 |- | + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/notice-bar/notice-bar](https://hellouniapp.dcloud.net.cn/pages/extUI/notice-bar/notice-bar) \ No newline at end of file diff --git a/uni_modules/uni-number-box/changelog.md b/uni_modules/uni-number-box/changelog.md new file mode 100644 index 0000000..ed3890d --- /dev/null +++ b/uni_modules/uni-number-box/changelog.md @@ -0,0 +1,18 @@ +## 1.1.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-04-20) +- 修复 uni-number-box 浮点数运算不精确的 bug +- 修复 uni-number-box change 事件触发不正确的 bug +- 新增 uni-number-box v-model 双向绑定 +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 + +## 1.0.7(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持 v-model +- 新增 支持 focus、blur 事件 +- 新增 支持 PC 端 diff --git a/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue b/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue new file mode 100644 index 0000000..e59fa40 --- /dev/null +++ b/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue @@ -0,0 +1,231 @@ + + + diff --git a/uni_modules/uni-number-box/package.json b/uni_modules/uni-number-box/package.json new file mode 100644 index 0000000..1a0a859 --- /dev/null +++ b/uni_modules/uni-number-box/package.json @@ -0,0 +1,81 @@ +{ + "id": "uni-number-box", + "displayName": "uni-number-box 数字输入框", + "version": "1.1.1", + "description": "NumberBox 带加减按钮的数字输入框组件,用户可以控制每次点击增加的数值,支持小数。", + "keywords": [ + "uni-ui", + "uniui", + "数字输入框" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-number-box/readme.md b/uni_modules/uni-number-box/readme.md new file mode 100644 index 0000000..9c951e4 --- /dev/null +++ b/uni_modules/uni-number-box/readme.md @@ -0,0 +1,50 @@ + + +## NumberBox 数字输入框 +> **组件名:uni-number-box** +> 代码块: `uNumberBox` + + +带加减按钮的数字输入框。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + + + +``` + +## API + +### NumberBox Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|value/v-model|Number |0 |输入框当前值 | +|min |Number |0 |最小值 | +|max |Number |100 |最大值 | +|step |Number |1 |每次点击改变的间隔大小 | +|disabled |Boolean|false |是否为禁用状态 | + +### NumberBox Events + +|事件名称 |说明 |返回值 | +|:-: |:-: |:-: | +|change |输入框值改变时触发的事件,参数为输入框当前的 value |- | +|focus |输入框聚焦时触发的事件,参数为 event 对象 |- | +|blur |输入框失焦时触发的事件,参数为 event 对象 |- | + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/number-box/number-box](https://hellouniapp.dcloud.net.cn/pages/extUI/number-box/number-box) \ No newline at end of file diff --git a/uni_modules/uni-pagination/changelog.md b/uni_modules/uni-pagination/changelog.md new file mode 100644 index 0000000..05387e0 --- /dev/null +++ b/uni_modules/uni-pagination/changelog.md @@ -0,0 +1,11 @@ +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-04-12) +- 新增 PC 和 移动端适配不同的 ui +## 1.0.5(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue b/uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue new file mode 100644 index 0000000..a883602 --- /dev/null +++ b/uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue @@ -0,0 +1,404 @@ + + + + + diff --git a/uni_modules/uni-pagination/package.json b/uni_modules/uni-pagination/package.json new file mode 100644 index 0000000..1b066ce --- /dev/null +++ b/uni_modules/uni-pagination/package.json @@ -0,0 +1,82 @@ +{ + "id": "uni-pagination", + "displayName": "uni-pagination 分页器", + "version": "1.1.0", + "description": "Pagination 分页器组件,用于展示页码、请求数据等。", + "keywords": [ + "uni-ui", + "uniui", + "分页器", + "页码" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-icons"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-pagination/readme.md b/uni_modules/uni-pagination/readme.md new file mode 100644 index 0000000..c8e47e3 --- /dev/null +++ b/uni_modules/uni-pagination/readme.md @@ -0,0 +1,48 @@ + + +## Pagination 分页器 +> **组件名:uni-pagination** +> 代码块: `uPagination` + + +分页器组件,用于展示页码、请求数据等。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + +``` + +## API + +### Pagination Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|prevText |String |上一页 |左侧按钮文字 | +|nextText |String |下一页 |右侧按钮文字 | +|current |Number |1 |当前页 | +|total |Number |0 |数据总量 | +|pageSize |Number |10 |每页数据量 | +|showIcon |Boolean|false |是否以 icon 形式展示按钮 | + + +### Pagination Events + +|事件称名 |说明 |返回值 | +|:-: |:-: |:-: | +|@change|点击页码按钮时触发 |e={type,current} current为当前页,type值为:next/prev,表示点击的是上一页还是下一个 | + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/pagination/pagination](https://hellouniapp.dcloud.net.cn/pages/extUI/pagination/pagination) \ No newline at end of file diff --git a/uni_modules/uni-popup/changelog.md b/uni_modules/uni-popup/changelog.md new file mode 100644 index 0000000..99454ba --- /dev/null +++ b/uni_modules/uni-popup/changelog.md @@ -0,0 +1,37 @@ +## 1.6.2(2021-08-24) +- 新增 支持国际化 +## 1.6.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.6.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.5.0(2021-06-23) +- 新增 mask-click 遮罩层点击事件 +## 1.4.5(2021-06-22) +- 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +## 1.4.4(2021-06-18) +- 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +## 1.4.3(2021-06-08) +- 修复 错误的 watch 字段 +- 修复 safeArea 属性不生效的问题 +- 修复 点击内容,再点击遮罩无法关闭的Bug +## 1.4.2(2021-05-12) +- 新增 组件示例地址 +## 1.4.1(2021-04-29) +- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题 +## 1.4.0 (2021-04-29) +- 新增 type 属性的 left\right 值,支持左右弹出 +- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗 +- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色 +- 新增 safeArea 属性,是否适配底部安全区 +- 修复 App\h5\微信小程序底部安全区占位不对的Bug +- 修复 App 端弹出等待的Bug +- 优化 提升低配设备性能,优化动画卡顿问题 +- 优化 更简单的组件自定义方式 +## 1.2.9(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.2.8(2021-02-05) +- 调整为uni_modules目录规范 +## 1.2.7(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持 PC 端 +- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端 diff --git a/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js b/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js new file mode 100644 index 0000000..6ef26a2 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + this.$once('hook:beforeDestroy', () => { + document.removeEventListener('keyup', listener) + }) + }, + render: () => {} +} +// #endif diff --git a/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue b/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue new file mode 100644 index 0000000..291634c --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue @@ -0,0 +1,263 @@ + + + + + diff --git a/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue b/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue new file mode 100644 index 0000000..f4c85e2 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue @@ -0,0 +1,143 @@ + + + + diff --git a/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue b/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue new file mode 100644 index 0000000..705902d --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue @@ -0,0 +1,185 @@ + + + + diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/en.json b/uni_modules/uni-popup/components/uni-popup/i18n/en.json new file mode 100644 index 0000000..7f1bd06 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/en.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "cancel", + "uni-popup.ok": "ok", + "uni-popup.placeholder": "pleace enter", + "uni-popup.title": "Hint", + "uni-popup.shareTitle": "Share to" +} diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/index.js b/uni_modules/uni-popup/components/uni-popup/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json new file mode 100644 index 0000000..5e3003c --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "取消", + "uni-popup.ok": "确定", + "uni-popup.placeholder": "请输入", + "uni-popup.title": "提示", + "uni-popup.shareTitle": "分享到" +} diff --git a/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json new file mode 100644 index 0000000..13e39eb --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "取消", + "uni-popup.ok": "確定", + "uni-popup.placeholder": "請輸入", + "uni-popup.title": "提示", + "uni-popup.shareTitle": "分享到" +} diff --git a/uni_modules/uni-popup/components/uni-popup/keypress.js b/uni_modules/uni-popup/components/uni-popup/keypress.js new file mode 100644 index 0000000..62dda46 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + // this.$once('hook:beforeDestroy', () => { + // document.removeEventListener('keyup', listener) + // }) + }, + render: () => {} +} +// #endif diff --git a/uni_modules/uni-popup/components/uni-popup/message.js b/uni_modules/uni-popup/components/uni-popup/message.js new file mode 100644 index 0000000..0ff9a02 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/message.js @@ -0,0 +1,22 @@ +export default { + created() { + if (this.type === 'message') { + // 不显示遮罩 + this.maskShow = false + // 获取子组件对象 + this.childrenMsg = null + } + }, + methods: { + customOpen() { + if (this.childrenMsg) { + this.childrenMsg.open() + } + }, + customClose() { + if (this.childrenMsg) { + this.childrenMsg.close() + } + } + } +} diff --git a/uni_modules/uni-popup/components/uni-popup/popup.js b/uni_modules/uni-popup/components/uni-popup/popup.js new file mode 100644 index 0000000..c4e5781 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/popup.js @@ -0,0 +1,26 @@ + +export default { + data() { + return { + + } + }, + created(){ + this.popup = this.getParent() + }, + methods:{ + /** + * 获取父元素实例 + */ + getParent(name = 'uniPopup') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + } +} diff --git a/uni_modules/uni-popup/components/uni-popup/share.js b/uni_modules/uni-popup/components/uni-popup/share.js new file mode 100644 index 0000000..462bb83 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/share.js @@ -0,0 +1,16 @@ +export default { + created() { + if (this.type === 'share') { + // 关闭点击 + this.mkclick = false + } + }, + methods: { + customOpen() { + console.log('share 打开了'); + }, + customClose() { + console.log('share 关闭了'); + } + } +} diff --git a/uni_modules/uni-popup/components/uni-popup/uni-popup.vue b/uni_modules/uni-popup/components/uni-popup/uni-popup.vue new file mode 100644 index 0000000..59698b7 --- /dev/null +++ b/uni_modules/uni-popup/components/uni-popup/uni-popup.vue @@ -0,0 +1,403 @@ + + + + diff --git a/uni_modules/uni-popup/package.json b/uni_modules/uni-popup/package.json new file mode 100644 index 0000000..1385af7 --- /dev/null +++ b/uni_modules/uni-popup/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-popup", + "displayName": "uni-popup 弹出层", + "version": "1.6.2", + "description": " Popup 组件,提供常用的弹层", + "keywords": [ + "uni-ui", + "弹出层", + "弹窗", + "popup", + "弹框" + ], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-transition" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} diff --git a/uni_modules/uni-popup/readme.md b/uni_modules/uni-popup/readme.md new file mode 100644 index 0000000..d8a0899 --- /dev/null +++ b/uni_modules/uni-popup/readme.md @@ -0,0 +1,296 @@ + + +## Popup 弹出层 +> **组件名:uni-popup** +> 代码块: `uPopup` +> 关联组件:`uni-popup-dialog`,`uni-popup-message`,`uni-popup-share`,`uni-transition` + + +弹出层组件,在应用中弹出一个消息提示窗口、提示框等 + + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - `uni-popup-message` 、 `uni-popup-dialog` 等扩展ui组件,需要和 `uni-popup` 配套使用,暂不支持单独使用 +> - `nvue` 中使用 `uni-popup` 时,尽量将组件置于其他元素后面,避免出现层级问题 +> - `uni-popup` 并不能完全阻止页面滚动,可在打开 `uni-popup` 的时候手动去做一些处理,禁止页面滚动 +> - 如果想在页面渲染完毕后就打开 `uni-popup` ,请在 `onReady` 或 `mounted` 生命周期内调用,确保组件渲染完毕 +> - 在微信小程序开发者工具中,启用真机调试,popup 会延时出现,是因为 setTimeout 在真机调试中的延时问题导致的,预览和发布小程序不会出现此问题 +> - 使用 `npm` 方式引入组件,如果确认引用正确,但是提示未注册组件或显示不正常,请尝试重新编译项目 +> - `uni-popup` 中尽量不要使用 `scroll-view` 嵌套过多的内容,可能会影响组件的性能,导致组件无法打开或者打开卡顿 +> - `uni-popup` 不会覆盖原生 tabbar 和原生导航栏 +> - 组件支持 nvue ,需要在 `manifest.json > app-plus` 节点下配置 `"nvueStyleCompiler" : "uni-app"` +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + + +### 基本用法 + +**示例** + +```html + +底部弹出 Popup +``` + +```javascript +export default { + methods:{ + open(){ + // 通过组件定义的ref调用uni-popup方法 ,如果传入参数 ,type 属性将失效 ,仅支持 ['top','left','bottom','right','center'] + this.$refs.popup.open('top') + } + } +} + +``` + +### 设置主窗口背景色 + +在大多数场景下,并不需要设置 `background-color` 属性,因为`uni-popup`的主窗口默认是透明的,在向里面插入内容的时候 ,样式完全交由用户定制,如果设置了背景色 ,例如 `uni-popup-dialog` 中的圆角就很难去实现,不设置背景色,更适合用户去自由发挥。 + +而也有特例,需要我们主动去设置背景色,例如 `type = 'bottom'` 的时候 ,在异型屏中遇到了底部安全区问题(如 iphone 11),因为 `uni-popup`的主要内容避开了安全区(设置`safe-area:true`),导致底部的颜色我们无法自定义,这时候使用 `background-color` 就可以解决这个问题。 + +**示例** + +```html + +底部弹出 Popup +``` + +### 禁用打开动画 +在某些场景 ,可能不希望弹层有动画效果 ,只需要将 `animation` 属性设置为 `false` 即可以关闭动画。 + +**示例** + +```html + +中间弹出 Popup +``` + +### 禁用点击遮罩关闭 +默认情况下,点击遮罩会自动关闭`uni-popup`,如不想点击关闭,只需将`mask-click`设置为`false`,这时候要关闭`uni-popup`,只能手动调用 `close` 方法。 + +**示例** + +```html + + + Popup + + +``` + +```javascript +export default { + data() { + return {} + }, + onReady() {}, + methods: { + open() { + this.$refs.popup.open('top') + }, + close() { + this.$refs.popup.close() + } + } +} + +``` + +## API + +### Popup Props + +|属性名|类型|默认值|说明| +|:-:|:-:|:-:|:-:| +|animation|Boolean|true|是否开启动画| +|type|String|'center'|弹出方式| +|mask-click|Boolean|true|蒙版点击是否关闭弹窗| +|background-color|String|'none'|主窗口背景色| +|safe-area|Boolean|true|是否适配底部安全区| + +#### Type Options + +|属性名|说明| +|:-:| :-:| +|top|顶部弹出 | +|center|居中弹出| +|bottom|底部弹出| +|left|左侧弹出| +|right|右侧弹出| +|message|预置样式 :消息提示| +|dialog|预置样式 :对话框| +|share|预置样式 :底部弹出分享示例 | + + +### Popup Methods + +|方法称名 |说明|参数| +|:-:|:-:|:-:| +|open|打开弹出层|open(String:type) ,如果参数可代替 type 属性| +|close|关闭弹出层 |-| + + +### Popup Events + +|事件称名|说明|返回值| +|:-:|:-:|:-:| +|change|组件状态发生变化触发|e={show: true|false,type:当前模式}| +|maskClick|点击遮罩层触发|-| + + +## 扩展组件说明 +`uni-popup` 其实并没有任何样式,只提供基础的动画效果,给用户一个弹出层解决方案,仅仅是这样并不能满足开发需求,所以我们提供了三种基础扩展样式 + +### uni-popup-message 提示信息 + +将 `uni-popup` 的`type`属性改为 `message`,并引入对应组件即可使用消息提示 ,*该组件不支持单独使用* + +**示例** + +```html + + + +``` + +### PopupMessage Props + +|属性名|类型|默认值|说明| +|:-:|:-:|:-:|:-:| +|type|String|success|消息提示主题| +|message|String|-|消息提示文字| +|duration|Number|3000|消息显示时间,超过显示时间组件自动关闭,设置为0 将不会关闭,需手动调用 close 方法关闭| + +#### Type Options + +|属性名|说明| +|:-:| :-:| +|success|成功 | +|warn|警告| +|error|失败| +|info|消息| + +### PopupMessage Slots + +|名称|说明| +|:-:|:-:| +|default|消息内容,会覆盖 message 属性| + +### uni-popup-dialog 对话框 + +将 `uni-popup` 的`type`属性改为 `dialog`,并引入对应组件即可使用对话框 ,*该组件不支持单独使用* + +**示例** + +```html + + + + +``` + +```javascript +export default { + methods: { + open() { + this.$refs.popup.open() + }, + /** + * 点击取消按钮触发 + * @param {Object} done + */ + close() { + // TODO 做一些其他的事情,before-close 为true的情况下,手动执行 close 才会关闭对话框 + // ... + this.$refs.popup.close() + }, + /** + * 点击确认按钮触发 + * @param {Object} done + * @param {Object} value + */ + confirm(value) { + // 输入框的值 + console.log(value) + // TODO 做一些其他的事情,手动执行 close 才会关闭对话框 + // ... + this.$refs.popup.close() + } + } +} +``` + +### PopupDialog Props + +|属性名|类型|默认值|说明| +|:-:|:-:|:-:|:-:| +|type|String|success|对话框标题主题,可选值: success/warn/info/error| +|mode|String|base| 对话框模式,可选值:base(提示对话框)/input(可输入对话框)| +|title|String|-|对话框标题| +|content|String|-|对话框内容,base模式下生效| +|value| String\Number|-|输入框默认值,input模式下生效| +|placeholder|String|-|输入框提示文字,input模式下生效| +|before-close|Boolean|false | 是否拦截按钮事件,如为true,则不会关闭对话框,关闭需要手动执行 uni-popup 的 close 方法| + +#### PopupDialog Events + +|事件称名 |说明|返回值| +|:-:|:-:|:-:| +|close|点击dialog取消按钮触发|-| +|confirm|点击dialog确定按钮触发|e={value:input模式下输入框的值}| + +### PopupDialog Slots + +|名称|说明| +|:-:|:-:| +|default|自定义内容,回覆盖原有的内容显示| + +### uni-popup-share 分享示例 + +分享示例,不作为最终可使用的组件,只做为样式组件,供用户自行修改,`后续的开发计划是实现实际的分享逻辑,参数可配置`。 + +将 `uni-popup` 的 `type` 属性改为 `share`,并引入对应组件即可使用 ,*该组件不支持单独使用* + +**示例** + +```html + + + +``` + +### PopupShare Props + +|属性名|类型|默认值|说明| +|:-:|:-:|:-:| :-: | +|title|String|-|分享弹窗标题| +|before-close|Boolean|false | 是否拦截按钮事件,如为true,则不会关闭对话框,关闭需要手动执行 uni-popup 的 close 方法| + +### PopupShare Events + +|事件称名|说明|返回值| +|:-:|:-:|:-:| +|select|选择触发|e = {item,index}:所选参数| + +**Tips** +- share 分享组件,只是作为一个扩展示例,如果需要修改数据源,请到组件内修改 + +## 帮助 +在使用中如遇到无法解决的问题,请提 [Issues](https://github.com/dcloudio/uni-ui/issues) 给我们。 + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/popup/popup](https://hellouniapp.dcloud.net.cn/pages/extUI/popup/popup) \ No newline at end of file diff --git a/uni_modules/uni-rate/changelog.md b/uni_modules/uni-rate/changelog.md new file mode 100644 index 0000000..efb03b7 --- /dev/null +++ b/uni_modules/uni-rate/changelog.md @@ -0,0 +1,18 @@ +## 1.2.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.2(2021-05-12) +- 新增 组件示例地址 +## 1.1.1(2021-04-21) +- 修复 布局变化后 uni-rate 星星计算不准确的 bug +- 优化 添加依赖 uni-icons, 导入 uni-rate 自动下载依赖 +## 1.1.0(2021-04-16) +- 修复 uni-rate 属性 margin 值为 string 组件失效的 bug + +## 1.0.9(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.8(2021-02-05) +- 调整为uni_modules目录规范 +- 支持 pc 端 diff --git a/uni_modules/uni-rate/components/uni-rate/uni-rate.vue b/uni_modules/uni-rate/components/uni-rate/uni-rate.vue new file mode 100644 index 0000000..58b5132 --- /dev/null +++ b/uni_modules/uni-rate/components/uni-rate/uni-rate.vue @@ -0,0 +1,393 @@ + + + + + diff --git a/uni_modules/uni-rate/package.json b/uni_modules/uni-rate/package.json new file mode 100644 index 0000000..c98df3d --- /dev/null +++ b/uni_modules/uni-rate/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-rate", + "displayName": "uni-rate 评分", + "version": "1.2.1", + "description": "Rate 评分组件,可自定义评分星星图标的大小、间隔、评分数。", + "keywords": [ + "uni-ui", + "uniui", + "评分" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} diff --git a/uni_modules/uni-rate/readme.md b/uni_modules/uni-rate/readme.md new file mode 100644 index 0000000..bb08800 --- /dev/null +++ b/uni_modules/uni-rate/readme.md @@ -0,0 +1,107 @@ + + +## Rate 评分 +> **组件名:uni-rate** +> 代码块: `uRate` +> 关联组件:`uni-icons` + + +评分组件,多用于购买商品后,对商品进行评价等场景 + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的使用说明,可以帮你避免一些错误。 +> - 暂时不支持零星选择 +> - 当前版本暂不支持修改图标,后续版本会继续优化 +> - 绑定值推荐使用 `v-model` 的方式 +> - 如需设置一个星星表示多分,如:显示5个星星,最高分10分。这种情况请在 change 事件监听中自行处理,获取到的值乘以你的基数就可以,默认组件是一星一分 + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + + +## 基本用法 + +```html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +```javascript + +export default { + components: {}, + data() { + return { + value: 2 + } + }, + methods: { + onChange(e) { + console.log('rate发生改变:' + JSON.stringify(e)) + } + } +} + +``` + +## API +### Rate Props + +属性名 | 类型 | 默认值 | 说明 +:-: | :-: | :-: | :-: +value/v-model | Number | 1 | 当前评分 +color | String | #ececec | 未选中状态的星星颜色 +activeColor | String | #ffca3e | 选中状态的星星颜色 +disabledColor | String | #c0c0c0 | 禁用状态的星星颜色 +size | Number | 24 | 星星的大小 +max | Number | 5 | 最大评分评分数量,目前一分一颗星 +margin | Number | 0 | 星星的间距,单位 px +isFill | Boolean | true | 星星的类型,是否为实心类型 +disabled | Boolean | false | 是否为禁用状态 (之前版本为已读状态,现更正为禁用状态) +readonly | Boolean | false | 是否为只读状态 +allowHalf | Boolean | false | 是否展示半星 +touchable | Boolean | true | 是否支持滑动手势 + +### Rate Events + +事件称名 | 说明 | 返回参数 +:-: | :-: | :-: +@change | 改变 value 的值返回 | e = { value:number } + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/rate/rate](https://hellouniapp.dcloud.net.cn/pages/extUI/rate/rate) \ No newline at end of file diff --git a/uni_modules/uni-row/changelog.md b/uni_modules/uni-row/changelog.md new file mode 100644 index 0000000..8a269ff --- /dev/null +++ b/uni_modules/uni-row/changelog.md @@ -0,0 +1,7 @@ +## 0.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.0.4(2021-05-12) +- 新增 组件示例地址 +## 0.0.3(2021-02-05) +- 调整为uni_modules目录规范 +- 新增uni-row组件 diff --git a/uni_modules/uni-row/components/uni-col/uni-col.vue b/uni_modules/uni-row/components/uni-col/uni-col.vue new file mode 100644 index 0000000..84e2deb --- /dev/null +++ b/uni_modules/uni-row/components/uni-col/uni-col.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/uni_modules/uni-row/components/uni-row/uni-row.vue b/uni_modules/uni-row/components/uni-row/uni-row.vue new file mode 100644 index 0000000..f8e8542 --- /dev/null +++ b/uni_modules/uni-row/components/uni-row/uni-row.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/uni_modules/uni-row/package.json b/uni_modules/uni-row/package.json new file mode 100644 index 0000000..0251ea7 --- /dev/null +++ b/uni_modules/uni-row/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-row", + "displayName": "uni-row 布局-行", + "version": "0.1.0", + "description": "流式栅格系统,随着屏幕或视口分为 24 份,可以迅速简便地创建布局。", + "keywords": [ + "uni-ui", + "uniui", + "栅格", + "布局", + "layout" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-row/readme.md b/uni_modules/uni-row/readme.md new file mode 100644 index 0000000..8013a9e --- /dev/null +++ b/uni_modules/uni-row/readme.md @@ -0,0 +1,183 @@ +## Layout 布局 + +> **组件名 uni-row、uni-col** +> 代码块: `uRow`、`uCol` + + +流式栅格系统,随着屏幕或视口分为 24 份,可以迅速简便地创建布局。 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - 如使用过程中有任何问题,或者您对 uni-ui 有一些好的建议,欢迎加入 uni-ui 交流群:871950839 +> + + +### 基本用法 + +###### 使用单一分栏创建基础的栅格布局 + +```html + + + + + + + + + + + + + + +``` + +### 分栏偏移 + +###### 支持偏移指定的栏数 + +```html + + + + + + + + + + + + + + + + + + + + +``` + +### 响应式布局 + +###### 共五个响应尺寸:xs、sm、md、lg 和 xl + +```html + + + + + + + + + + + + + + +``` + +###### 使用到的 CSS + +```css +.demo-uni-row { + margin-bottom: 10px; + /* QQ、字节小程序文档写有 :host,但实测不生效 */ + /* 百度小程序没有 :host,需要设置block */ + /* #ifdef MP-TOUTIAO || MP-QQ || MP-BAIDU */ + display: block; + /* #endif */ +} + +/* 支付宝小程序没有 demo-uni-row 层级 */ +/* 微信小程序使用了虚拟化节点,没有 demo-uni-row 层级 */ +/* #ifdef MP-ALIPAY || MP-WEIXIN */ +/deep/ .uni-row { + margin-bottom: 10px; +} +/* #endif */ + +.demo-uni-col { + height: 36px; + border-radius: 4px; +} + +.dark_deep { + background-color: #99a9bf; +} + +.dark { + background-color: #d3dce6; +} + +.light { + background-color: #e5e9f2; +} +``` + +### 平台差异说明 + +### `uni-row` +| 属性名 | App(nvue) | App(vue) | H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节跳动小程序 | QQ 小程序 | +| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | +| gutter | - | √ | √ | √ | √ | √ | √ | √ | + +### `uni-col` + +| 属性名 | App(nvue) | App(vue) | H5 | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节跳动小程序 | QQ 小程序 | +| :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | +| span | √ | √ | √ | √ | √ | √ | √ | √ | +| offset | √ | √ | √ | √ | √ | √ | √ | √ | +| push | √ | √ | √ | √ | √ | √ | √ | √ | +| pull | √ | √ | √ | √ | √ | √ | √ | √ | +| xs | - | √ | √ | √ | √ | √ | √ | √ | +| sm | - | √ | √ | √ | √ | √ | √ | √ | +| md | - | √ | √ | √ | √ | √ | √ | √ | +| lg | - | √ | √ | √ | √ | √ | √ | √ | +| xl | - | √ | √ | √ | √ | √ | √ | √ | + +## API + +### Row Props + +`其他平台` + +| 属性名 | 类型 | 可选值 | 默认值 | 必填 | 说明 | +| :-: | :-: | :-: | :-: | :-: | :-: | +| gutter | Number | - | 0 | 否 | 栅格间隔 | + +`nvue平台` + +| 属性名 | 类型 | 可选值 | 默认值 | 必填 | 说明 | +| :-: | :-: | :-: | :-: | :-: | :-: | +| width | Number/String | - | `750rpx` | 否 | nvue 中无百分比 width,使用 span 等属性时,需配置此项`rpx值`。此项不会影响其他平台展示效果 | + +### Col Props + +| 属性名 | 类型 | 可选值 | 默认值 | 必填 | 说明 | +| :-: | :-: | :-: | :-: | :-: | :-: | +| span | Number | - | 24 | 否 | 栅格占据的列数 | +| offset | Number | - | - | 否 | 栅格左侧间隔格数 | +| push | Number | - | - | 否 | 栅格向右偏移格数 | +| pull | Number | - | - | 否 | 栅格向左偏移格数 | +| xs | Number/object | - | - | 否 | 屏幕宽度`<768px`时,要显示的栅格规则,如:`:xs="8"`或`:xs="{span: 8, pull: 4}"` | +| sm | Number/object | - | - | 否 | 屏幕宽度`≥768px`时,要显示的栅格规则 | +| md | Number/object | - | - | 否 | 屏幕宽度`≥992px`时,要显示的栅格规则 | +| lg | Number/object | - | - | 否 | 屏幕宽度`≥1200px`时,要显示的栅格规则 | +| xl | Number/object | - | - | 否 | 屏幕宽度`≥1920px`时,要显示的栅格规则 | + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/row/row](https://hellouniapp.dcloud.net.cn/pages/extUI/row/row) \ No newline at end of file diff --git a/uni_modules/uni-search-bar/changelog.md b/uni_modules/uni-search-bar/changelog.md new file mode 100644 index 0000000..b71a10d --- /dev/null +++ b/uni_modules/uni-search-bar/changelog.md @@ -0,0 +1,22 @@ +## 1.1.1(2021-08-24) +- 新增 支持国际化 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.9(2021-05-12) +- 新增 项目示例地址 +## 1.0.8(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.7(2021-04-15) +- uni-ui 新增 uni-search-bar 的 focus 事件 + +## 1.0.6(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持双向绑定 +- 更改 input 事件的返回值,e={value:Number} --> e=value +- 新增 支持图标插槽 +- 新增 支持 clear、blur 事件 +- 新增 支持 focus 属性 +- 去掉组件背景色 diff --git a/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json new file mode 100644 index 0000000..dd083a5 --- /dev/null +++ b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "cancel", + "uni-search-bar.placeholder": "Search enter content" +} \ No newline at end of file diff --git a/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json new file mode 100644 index 0000000..d4e5c12 --- /dev/null +++ b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "cancel", + "uni-search-bar.placeholder": "请输入搜索内容" +} diff --git a/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json new file mode 100644 index 0000000..318b6ef --- /dev/null +++ b/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "cancel", + "uni-search-bar.placeholder": "請輸入搜索內容" +} diff --git a/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue b/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue new file mode 100644 index 0000000..3c67344 --- /dev/null +++ b/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue @@ -0,0 +1,282 @@ + + + + + diff --git a/uni_modules/uni-search-bar/package.json b/uni_modules/uni-search-bar/package.json new file mode 100644 index 0000000..f9b9bad --- /dev/null +++ b/uni_modules/uni-search-bar/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-search-bar", + "displayName": "uni-search-bar 搜索栏", + "version": "1.1.1", + "description": "搜索栏组件,通常用于搜索商品、文章等", + "keywords": [ + "uni-ui", + "uniui", + "搜索框", + "搜索栏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-search-bar/readme.md b/uni_modules/uni-search-bar/readme.md new file mode 100644 index 0000000..b39d6cf --- /dev/null +++ b/uni_modules/uni-search-bar/readme.md @@ -0,0 +1,86 @@ + + +## SearchBar 搜索栏 + +> **组件名:uni-search-bar** +> 代码块: `uSearchBar` + + +评分组件 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中使用组件 + +```html + + + + + + + + + + + +``` + + +## API +### SearchBar Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|value/v-model |StringNumber | |搜索栏绑定值 | +|placeholder |String |搜索 |搜索栏Placeholder | +|radius |Number |10 |搜索栏圆角,单位px | +|clearButton |String |auto |是否显示清除按钮,可选值`always`-一直显示、`auto`-输入框不为空时显示、`none`-一直不显示 | +|cancelButton |String |auto |是否显示取消按钮,可选值`always`-一直显示、`auto`-输入框不为空时显示、`none`-一直不显示 | +|cancelText |String |取消 |取消按钮的文字 | +|bgColor |String |#F8F8F8|输入框背景颜色 | +|maxlength |Number |100 |输入最大长度 | +|focus |Boolean |false | | + + +### SearchBar Events + +|事件称名 |说明 |返回参数 | +|:-: |:-: |:-: | +|@confirm |uniSearchBar 的输入框 confirm 事件,返回参数为uniSearchBar的value |e={value:Number} | +|@input |uniSearchBar 的 value 改变时触发事件,返回参数为uniSearchBar的value|e=value | +|@cancel |点击取消按钮时触发事件,返回参数为uniSearchBar的value |e={value:Number} | +|@clear |点击清除按钮时触发事件,返回参数为uniSearchBar的value |e={value:Number} | +|@focus |input 获取焦点时触发事件,返回参数为uniSearchBar的value |e={value:Number} | +|@blur |input 失去焦点时触发事件,返回参数为uniSearchBar的value |e={value:Number} | + +### 替换 icon 的 slot 插槽 + +|插槽称名 |说明 | +|:-: |:-: | +|searchIcon |替换组件的搜索图标| +|clearIcon |替换组件的清除图标| + +```html + + + + + + + + X + + +``` + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/search-bar/search-bar](https://hellouniapp.dcloud.net.cn/pages/extUI/search-bar/search-bar) \ No newline at end of file diff --git a/uni_modules/uni-segmented-control/changelog.md b/uni_modules/uni-segmented-control/changelog.md new file mode 100644 index 0000000..e5093b3 --- /dev/null +++ b/uni_modules/uni-segmented-control/changelog.md @@ -0,0 +1,6 @@ +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.5(2021-05-12) +- 新增 项目示例地址 +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue b/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue new file mode 100644 index 0000000..b753366 --- /dev/null +++ b/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/uni_modules/uni-segmented-control/package.json b/uni_modules/uni-segmented-control/package.json new file mode 100644 index 0000000..b3ccdd2 --- /dev/null +++ b/uni_modules/uni-segmented-control/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-segmented-control", + "displayName": "uni-segmented-control 分段器", + "version": "1.1.0", + "description": "分段器由至少 2 个分段控件组成,用作不同视图的显示", + "keywords": [ + "uni-ui", + "uniui", + "分段器", + "segement", + "顶部选择" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-segmented-control/readme.md b/uni_modules/uni-segmented-control/readme.md new file mode 100644 index 0000000..4779016 --- /dev/null +++ b/uni_modules/uni-segmented-control/readme.md @@ -0,0 +1,60 @@ + + +## SegmentedControl 分段器 +> **组件名:uni-segmented-control** +> 代码块: `uSegmentedControl` + + +用作不同视图的显示 + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +### 基本用法 + +在 ``template`` 中的使用 + +```html + +``` + +## API + +### SegmentedControl Props + +|属性名 |类型 |默认值 |说明 | +|:-: |:-: |:-: |:-: | +|current |Number |0 |当前选中的tab索引值,从0计数 | +|styleType |String |button |分段器样式类型,可选值:button(按钮类型),text(文字类型) | +|activeColor |String |#007aff|选中的标签背景色与边框颜色 | +|values |Array |- |选项数组 | + +### SegmentedControl Events + +|事件名 |说明 |返回值 | +|:-: |:-: |:-: | +|@clickItem |组件触发点击事件时触发 |e={currentIndex} | + + + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/segmented-control/segmented-control](https://hellouniapp.dcloud.net.cn/pages/extUI/segmented-control/segmented-control) \ No newline at end of file diff --git a/uni_modules/uni-share/changelog.md b/uni_modules/uni-share/changelog.md new file mode 100644 index 0000000..e3cd23f --- /dev/null +++ b/uni_modules/uni-share/changelog.md @@ -0,0 +1,14 @@ +## 2.0.0(2021-10-14) +支持监听返回操作(如:物理返回,全面屏手机侧滑)关闭分享弹窗 +## 1.0.6(2021-08-25) +兼容vue3 +## 1.0.5(2021-08-05) +优化代码实现,修改原来用`eval()`函数实现的逻辑 +## 1.0.4(2021-06-07) +为符合苹果应用市场的审核,只显示存在对应的分享客户端的选项。如:配置包含微信分享,但是用户手机上并没有安装微信,就不显示微信分享。 +## 1.0.2(2021-05-06) +修复错误的提示:“打包时未添加oauth模块” +## 1.0.1(2021-04-30) +新增完整示例 +## 1.0.0(2021-04-28) +第1版发布 diff --git a/uni_modules/uni-share/js_sdk/uni-image-menu.js b/uni_modules/uni-share/js_sdk/uni-image-menu.js new file mode 100644 index 0000000..a15a846 --- /dev/null +++ b/uni_modules/uni-share/js_sdk/uni-image-menu.js @@ -0,0 +1,203 @@ +var nvMask, nvImageMenu; +class NvImageMenu { + constructor(arg) { + this.isShow = false + } + show({ + list, + cancelText + }, callback) { + if (!list) { + list = [{ + "img": "/static/sharemenu/wechatfriend.png", + "text": "图标文字" + }] + } + //以下为计算菜单的nview绘制布局,为固定算法,使用者无关关心 + var screenWidth = plus.screen.resolutionWidth + //以360px宽度屏幕为例,上下左右边距及2排按钮边距留25像素,图标宽度55像素,同行图标间的间距在360宽的屏幕是30px,但需要动态计算,以此原则计算4列图标分别的left位置 + //图标下的按钮文字距离图标5像素,文字大小12像素 + //底部取消按钮高度固定为44px + //TODO 未处理横屏和pad,这些情况6个图标应该一排即可 + var margin = 20, + iconWidth = 60, + icontextSpace = 5, + textHeight = 12 + var left1 = margin / 360 * screenWidth + var iconSpace = (screenWidth - (left1 * 2) - (iconWidth * 4)) / 3 //屏幕宽度减去左右留白间距,再减去4个图标的宽度,就是3个同行图标的间距 + if (iconSpace <= 5) { //屏幕过窄时,缩小边距和图标大小,再算一次 + margin = 15 + iconWidth = 40 + left1 = margin / 360 * screenWidth + iconSpace = (screenWidth - (left1 * 2) - (iconWidth * 4)) / 3 //屏幕宽度减去左右留白间距,再减去4个图标的宽度,就是3个同行图标的间距 + } + var left2 = left1 + iconWidth + iconSpace + var left3 = left1 + (iconWidth + iconSpace) * 2 + var left4 = left1 + (iconWidth + iconSpace) * 3 + var top1 = left1 + var top2 = top1 + iconWidth + icontextSpace + textHeight + left1 + + const TOP = { + top1, + top2 + }, + LEFT = { + left1, + left2, + left3, + left4 + }; + + nvMask = new plus.nativeObj.View("nvMask", { //先创建遮罩层 + top: '0px', + left: '0px', + height: '100%', + width: '100%', + backgroundColor: 'rgba(0,0,0,0.2)' + }); + nvImageMenu = new plus.nativeObj.View("nvImageMenu", { //创建底部图标菜单 + bottom: '0px', + left: '0px', + height: (iconWidth + textHeight + 2 * margin) * Math.ceil(list.length / 4) + 44 + + 'px', //'264px', + width: '100%', + backgroundColor: 'rgb(255,255,255)' + }); + nvMask.addEventListener("click", () => { //处理遮罩层点击 + // console.log('处理遮罩层点击'); + this.hide() + callback({ + event: "clickMask" + }) + }) + let myList = [] + list.forEach((item, i) => { + myList.push({ + tag: 'img', + src: item.img, + position: { + top: TOP['top' + (parseInt(i / 4) + 1)], + left: LEFT['left' + (1 + i % 4)], + width: iconWidth, + height: iconWidth + } + }) + myList.push({ + tag: 'font', + text: item.text, + textStyles: { + size: textHeight + }, + position: { + top: TOP['top' + (parseInt(i / 4) + 1)] + iconWidth + icontextSpace, + left: LEFT['left' + (1 + i % 4)], + width: iconWidth, + height: textHeight + } + }) + }) + + //绘制底部图标菜单的内容 + nvImageMenu.draw([{ + tag: 'rect', //菜单顶部的分割灰线 + color: '#e7e7e7', + position: { + top: '0px', + height: '1px' + } + }, + { + tag: 'font', + text: cancelText, //底部取消按钮的文字 + textStyles: { + size: '14px' + }, + position: { + bottom: '0px', + height: '44px' + } + }, + { + tag: 'rect', //底部取消按钮的顶部边线 + color: '#e7e7e7', + position: { + bottom: '45px', + height: '1px' + } + }, + ...myList + ]) + nvMask.show() + nvImageMenu.show() + // 开始动画 + /* + plus.nativeObj.View.startAnimation({ + type: 'slide-in-bottom', + duration: 300 + }, nvImageMenu, {}, function() { + console.log('plus.nativeObj.View.startAnimation动画结束'); + // 关闭原生动画 + plus.nativeObj.View.clearAnimation(); + nvImageMenu.show() + }); + */ + + + this.isShow = true + nvImageMenu.addEventListener("click", e => { //处理底部图标菜单的点击事件,根据点击位置触发不同的逻辑 + // console.log("click menu"+JSON.stringify(e)); + if (e.screenY > plus.screen.resolutionHeight - 44) { //点击了底部取消按钮 + // callback({event:"clickCancelButton"}) + this.hide() + } else if (e.clientX < 5 || e.clientX > screenWidth - 5 || e.clientY < 5) { + //屏幕左右边缘5像素及菜单顶部5像素不处理点击 + } else { //点击了图标按钮 + var iClickIndex = -1 //点击的图标按钮序号,第一个图标按钮的index为0 + var iRow = e.clientY < (top2 - (left1 / 2)) ? 0 : 1 + var iCol = -1 + if (e.clientX < (left2 - (iconSpace / 2))) { + iCol = 0 + } else if (e.clientX < (left3 - (iconSpace / 2))) { + iCol = 1 + } else if (e.clientX < (left4 - (iconSpace / 2))) { + iCol = 2 + } else { + iCol = 3 + } + if (iRow == 0) { + iClickIndex = iCol + } else { + iClickIndex = iCol + 4 + } + // console.log("点击按钮的序号: " + iClickIndex); + // if (iClickIndex >= 0 && iClickIndex <= 5) { //处理具体的点击逻辑,此处也可以自行定义逻辑。如果增减了按钮,此处也需要跟着修改 + // } + callback({ + event: "clickMenu", + index: iClickIndex + }) + } + }) + /* nvImageMenu.addEventListener("touchstart", function(e) { + if (e.screenY > (plus.screen.resolutionHeight - 44)) { + //TODO 这里可以处理按下背景变灰的效果 + } + }) + nvImageMenu.addEventListener("touchmove", function(e) { + //TODO 这里可以处理按下背景变灰的效果 + if (e.screenY > plus.screen.resolutionHeight - 44) {} + }) + nvImageMenu.addEventListener("touchend", function(e) { + //TODO 这里可以处理释放背景恢复的效果 + }) + */ + } + + hide() { + nvMask.hide() + nvImageMenu.hide() + this.isShow = false + } +} + +export default NvImageMenu diff --git a/uni_modules/uni-share/js_sdk/uni-share.js b/uni_modules/uni-share/js_sdk/uni-share.js new file mode 100644 index 0000000..8af8631 --- /dev/null +++ b/uni_modules/uni-share/js_sdk/uni-share.js @@ -0,0 +1,98 @@ +import UniImageMenu from './uni-image-menu.js'; +class UniShare extends UniImageMenu{ + constructor(arg) { + super() + this.isShow = super.isShow + } + async show(param, callback){ + var menus = [] + plus.share.getServices(services => { //只显示有服务的项目 + services = services.filter(item => item.nativeClient) + let servicesList = services.map(e => e.id) + param.menus.forEach(item => { + if (servicesList.includes(item.share.provider) || typeof(item.share) == 'string') { + menus.push(item) + } + }) + super.show({ + list: menus, + cancelText: param.cancelText + }, e => { + callback(e) + if(e.event == 'clickMenu'){ + if (typeof(menus[e.index]['share']) == 'string') { + this[menus[e.index]['share']](param) + } else { + uni.share({ + ...param.content, + ...menus[e.index].share, + success: res=> { + console.log("success:" + JSON.stringify(res)); + super.hide() + }, + fail: function(err) { + console.log("fail:" + JSON.stringify(err)); + uni.showModal({ + content: JSON.stringify(err), + showCancel: false, + confirmText: "知道了" + }); + } + }) + } + } + }) + }, err => { + uni.showModal({ + title: '获取服务供应商失败:' + JSON.stringify(err), + showCancel: false, + confirmText: '知道了' + }); + console.error('获取服务供应商失败:' + JSON.stringify(err)); + }) + } + hide(){ + super.hide() + } + copyurl(param) { + console.log('copyurl',param); + uni.setClipboardData({ + data: param.content.href, + success: ()=>{ + console.log('success'); + uni.hideToast() //关闭自带的toast + uni.showToast({ + title: '复制成功', + icon: 'none' + }); + super.hide(); + }, + fail: (err) => { + uni.showModal({ + content: JSON.stringify(err), + showCancel: false + }); + } + }); + } + // 使用系统分享发送分享消息 + shareSystem(param) { + console.log('shareSystem',param); + plus.share.sendWithSystem({ + type: 'text', + content: param.content.title + param.content.summary || '', + href: param.content.href, + }, (e)=> { + console.log('分享成功'); + super.hide() + }, (err)=> { + console.log('分享失败:' + JSON.stringify(err)); + uni.showModal({ + title: '获取服务供应商失败:' + JSON.stringify(err), + showCancel: false, + confirmText: '知道了' + }); + }); + } +} +export default UniShare \ No newline at end of file diff --git a/uni_modules/uni-share/package.json b/uni_modules/uni-share/package.json new file mode 100644 index 0000000..4a2a3e1 --- /dev/null +++ b/uni_modules/uni-share/package.json @@ -0,0 +1,80 @@ +{ + "id": "uni-share", + "displayName": "uni-share", + "version": "2.0.0", + "description": "底部弹出宫格图标式的分享菜单,可覆盖原生组件。", + "keywords": [ + "分享菜单" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "JS SDK", + "通用 SDK" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "n", + "Android Browser": "n", + "微信浏览器(Android)": "n", + "QQ浏览器(Android)": "n" + }, + "H5-pc": { + "Chrome": "n", + "IE": "n", + "Edge": "n", + "Firefox": "n", + "Safari": "n" + }, + "小程序": { + "微信": "n", + "阿里": "n", + "百度": "n", + "字节跳动": "n", + "QQ": "n" + }, + "快应用": { + "华为": "n", + "联盟": "n" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-share/readme.md b/uni_modules/uni-share/readme.md new file mode 100644 index 0000000..a3d201c --- /dev/null +++ b/uni_modules/uni-share/readme.md @@ -0,0 +1,85 @@ +#### 本功能基于[底部图标菜单](https://ext.dcloud.net.cn/plugin?id=4858)封装而成。 +### 示例代码 +``` + + + +``` \ No newline at end of file diff --git a/uni_modules/uni-sign-in/changelog.md b/uni_modules/uni-sign-in/changelog.md new file mode 100644 index 0000000..c09f3de --- /dev/null +++ b/uni_modules/uni-sign-in/changelog.md @@ -0,0 +1,6 @@ +## 1.0.2(2021-08-25) +修复时区问题 +## 1.0.1(2021-08-23) +调整签到逻辑,支持查出积分的收入支出历史记录 +## 1.0.0(2021-08-05) +1.0.0版本发布 diff --git a/uni_modules/uni-sign-in/components/uni-sign-in/uni-sign-in.vue b/uni_modules/uni-sign-in/components/uni-sign-in/uni-sign-in.vue new file mode 100644 index 0000000..99d5591 --- /dev/null +++ b/uni_modules/uni-sign-in/components/uni-sign-in/uni-sign-in.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/uni_modules/uni-sign-in/package.json b/uni_modules/uni-sign-in/package.json new file mode 100644 index 0000000..2751a88 --- /dev/null +++ b/uni_modules/uni-sign-in/package.json @@ -0,0 +1,82 @@ +{ + "id": "uni-sign-in", + "displayName": "签到插件", + "version": "1.0.2", + "description": "uni-sign-in", + "keywords": [ + "uni-sign-in", + "签到", + "营销" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "uniCloud", + "云端一体页面模板" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "u", + "微信浏览器(Android)": "u", + "QQ浏览器(Android)": "u" + }, + "H5-pc": { + "Chrome": "y", + "IE": "u", + "Edge": "u", + "Firefox": "u", + "Safari": "u" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/uni-sign-in/pages/demo/demo.vue b/uni_modules/uni-sign-in/pages/demo/demo.vue new file mode 100644 index 0000000..4efee8a --- /dev/null +++ b/uni_modules/uni-sign-in/pages/demo/demo.vue @@ -0,0 +1,12 @@ +