From 01f091fcd863a19a114ad4bc32ead42b87586e94 Mon Sep 17 00:00:00 2001
From: kele <1184860403@qq.com>
Date: Tue, 22 Nov 2022 15:45:36 +0800
Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E5=B8=83v1.0.3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.env.example | 14 +
.gitignore | 9 +
.prettierignore | 7 +
.prettierrc | 10 +
App.vue | 34 +
androidPrivacy.json | 3 +
index.html | 17 +
jsconfig.json | 9 +
main.js | 15 +
manifest.json | 224 +++
package.json | 104 ++
pages.json | 753 +++++++++
pages/activity/groupon/detail.vue | 505 +++++++
pages/activity/groupon/list.vue | 253 ++++
pages/activity/groupon/order.vue | 281 ++++
pages/activity/index.vue | 191 +++
pages/activity/seckill/list.vue | 252 ++++
pages/app/score-shop.vue | 81 +
pages/app/sign.vue | 540 +++++++
pages/chat/components/goods.vue | 63 +
pages/chat/components/order.vue | 122 ++
pages/chat/components/select-popup.vue | 151 ++
pages/chat/emoji.js | 58 +
pages/chat/index.vue | 851 +++++++++++
pages/chat/socket.js | 821 ++++++++++
pages/commission/apply.vue | 285 ++++
pages/commission/components/account-info.vue | 108 ++
.../commission/components/commission-auth.vue | 184 +++
.../components/commission-condition.vue | 173 +++
.../commission/components/commission-info.vue | 125 ++
.../commission/components/commission-log.vue | 181 +++
.../commission/components/commission-menu.vue | 153 ++
pages/commission/goods.vue | 137 ++
pages/commission/index.vue | 61 +
pages/commission/order.vue | 416 +++++
pages/commission/share-log.vue | 173 +++
pages/commission/team.vue | 261 ++++
pages/coupon/detail.vue | 371 +++++
pages/coupon/list.vue | 244 +++
pages/goods/comment/add.vue | 157 ++
pages/goods/comment/list.vue | 182 +++
.../goods/components/detail/comment-item.vue | 74 +
.../components/detail/detail-activity-tip.vue | 92 ++
.../components/detail/detail-cell-params.vue | 115 ++
.../components/detail/detail-cell-service.vue | 118 ++
.../components/detail/detail-cell-sku.vue | 35 +
pages/goods/components/detail/detail-cell.vue | 60 +
.../components/detail/detail-comment-card.vue | 101 ++
.../components/detail/detail-content-card.vue | 51 +
.../goods/components/detail/detail-navbar.vue | 255 ++++
.../components/detail/detail-progress.vue | 39 +
.../components/detail/detail-skeleton.vue | 177 +++
.../goods/components/detail/detail-tabbar.vue | 171 +++
.../components/groupon/groupon-card-list.vue | 137 ++
.../goods/components/list/list-goods-card.vue | 103 ++
pages/goods/components/list/list-navbar.vue | 92 ++
pages/goods/groupon.vue | 572 +++++++
pages/goods/index.vue | 399 +++++
pages/goods/list.vue | 376 +++++
pages/goods/score.vue | 354 +++++
pages/goods/seckill.vue | 528 +++++++
pages/index/cart.vue | 203 +++
pages/index/category.vue | 250 +++
pages/index/components/first-one.vue | 29 +
pages/index/components/first-two.vue | 69 +
pages/index/components/second-one.vue | 73 +
pages/index/components/third-one.vue | 92 ++
pages/index/index.vue | 67 +
pages/index/login.vue | 39 +
pages/index/page.vue | 51 +
pages/index/search.vue | 116 ++
pages/index/user.vue | 42 +
pages/order/aftersale/apply.vue | 353 +++++
pages/order/aftersale/detail.vue | 350 +++++
pages/order/aftersale/list.vue | 236 +++
pages/order/aftersale/log.vue | 112 ++
pages/order/confirm.vue | 404 +++++
pages/order/detail.vue | 541 +++++++
pages/order/express/list.vue | 104 ++
pages/order/express/log.vue | 174 +++
pages/order/invoice.vue | 328 ++++
pages/order/list.vue | 585 +++++++
pages/pay/components/account-info-modal.vue | 237 +++
pages/pay/components/account-type-select.vue | 186 +++
pages/pay/components/add-alipay.vue | 112 ++
pages/pay/components/transfter-balance.vue | 94 ++
pages/pay/index.vue | 290 ++++
pages/pay/recharge-log.vue | 171 +++
pages/pay/recharge.vue | 264 ++++
pages/pay/result.vue | 256 ++++
pages/pay/withdraw-log.vue | 187 +++
pages/pay/withdraw.vue | 380 +++++
pages/public/error.vue | 57 +
pages/public/faq.vue | 110 ++
pages/public/feedback.vue | 228 +++
pages/public/richtext.vue | 47 +
pages/public/setting.vue | 239 +++
pages/public/webview.vue | 15 +
pages/user/address/edit.vue | 264 ++++
pages/user/address/list.vue | 159 ++
pages/user/goods-collect.vue | 238 +++
pages/user/goods-log.vue | 288 ++++
pages/user/info.vue | 503 +++++++
pages/user/invoice/edit.vue | 277 ++++
pages/user/invoice/list.vue | 82 +
pages/user/set.vue | 88 ++
pages/user/wallet/commission.vue | 499 ++++++
pages/user/wallet/money.vue | 348 +++++
pages/user/wallet/score.vue | 280 ++++
sheep/api/activity.js | 43 +
sheep/api/app.js | 111 ++
sheep/api/cart.js | 41 +
sheep/api/category.js | 10 +
sheep/api/chat.js | 13 +
sheep/api/commission.js | 65 +
sheep/api/coupon.js | 38 +
sheep/api/data.js | 19 +
sheep/api/goods.js | 79 +
sheep/api/index.js | 10 +
sheep/api/order.js | 130 ++
sheep/api/pay.js | 44 +
sheep/api/third.js | 127 ++
sheep/api/trade.js | 37 +
sheep/api/user.js | 323 ++++
.../s-activity-pop/s-activity-pop.vue | 96 ++
.../s-address-item/s-address-item.vue | 105 ++
.../s-auth-modal/components/account-login.vue | 108 ++
.../s-auth-modal/components/change-mobile.vue | 120 ++
.../components/change-password.vue | 121 ++
.../components/change-username.vue | 72 +
.../components/reset-password.vue | 114 ++
.../s-auth-modal/components/sms-login.vue | 115 ++
.../s-auth-modal/components/sms-register.vue | 126 ++
sheep/components/s-auth-modal/index.scss | 151 ++
.../components/s-auth-modal/s-auth-modal.vue | 172 +++
.../components/s-block-item/s-block-item.vue | 53 +
sheep/components/s-block/s-block.vue | 50 +
.../s-coupon-block/s-coupon-block.vue | 189 +++
.../s-coupon-card/s-coupon-card.vue | 80 +
.../components/s-coupon-get/s-coupon-get.vue | 108 ++
.../s-coupon-list/s-coupon-list.vue | 184 +++
.../s-coupon-select/s-coupon-select.vue | 109 ++
.../components/navbar-item.vue | 62 +
.../s-custom-navbar/components/navbar.vue | 314 ++++
.../s-custom-navbar/s-custom-navbar.vue | 117 ++
.../s-discount-list/s-discount-list.vue | 114 ++
sheep/components/s-empty/s-empty.vue | 93 ++
.../components/s-float-menu/s-float-menu.vue | 81 +
.../components/s-goods-card/s-goods-card.vue | 250 +++
.../s-goods-column/s-goods-column.vue | 711 +++++++++
.../components/s-goods-item/s-goods-item.vue | 172 +++
.../s-goods-scroll/s-goods-scroll.vue | 32 +
.../s-goods-shelves/s-goods-shelves.vue | 147 ++
.../s-groupon-block/s-groupon-block.vue | 156 ++
.../s-image-banner/s-image-banner.vue | 39 +
.../s-image-block/s-image-block.vue | 26 +
.../components/s-image-cube/s-image-cube.vue | 109 ++
.../s-invoice-item/s-invoice-item.vue | 96 ++
sheep/components/s-layout/s-layout.vue | 244 +++
.../components/s-line-block/s-line-block.vue | 14 +
.../s-menu-button/s-menu-button.vue | 368 +++++
sheep/components/s-menu-grid/s-menu-grid.vue | 81 +
sheep/components/s-menu-list/s-menu-list.vue | 65 +
.../components/s-menu-tools/s-menu-tools.vue | 122 ++
.../s-notice-block/s-notice-block.vue | 37 +
.../components/s-order-card/s-order-card.vue | 102 ++
.../s-popup-image/s-popup-image.vue | 73 +
.../s-richtext-block/s-richtext-block.vue | 38 +
.../s-score-block/s-score-block.vue | 202 +++
.../components/s-score-card/s-score-card.vue | 459 ++++++
.../s-search-block/s-search-block.vue | 158 ++
.../s-seckill-block/s-seckill-block.vue | 158 ++
.../s-select-groupon-sku.vue | 569 +++++++
.../s-select-seckill-sku.vue | 433 ++++++
.../components/s-select-sku/s-select-sku.vue | 460 ++++++
.../s-share-modal/canvas-poster/index.vue | 156 ++
.../canvas-poster/poster/goods.js | 119 ++
.../canvas-poster/poster/groupon.js | 112 ++
.../canvas-poster/poster/index.js | 14 +
.../canvas-poster/poster/user.js | 60 +
.../s-share-modal/canvas-poster/useCanvas.js | 89 ++
.../s-share-modal/s-share-modal.vue | 201 +++
sheep/components/s-statusbar/s-statusbar.vue | 10 +
sheep/components/s-tabbar/s-tabbar.vue | 88 ++
.../s-title-block/s-title-block.vue | 114 ++
.../s-uploader/choose-and-upload-file.js | 213 +++
sheep/components/s-uploader/s-uploader.vue | 674 +++++++++
sheep/components/s-uploader/upload-file.vue | 335 +++++
sheep/components/s-uploader/upload-image.vue | 302 ++++
sheep/components/s-uploader/utils.js | 110 ++
sheep/components/s-user-card/s-user-card.vue | 164 ++
.../s-video-block/s-video-block.vue | 31 +
.../s-wallet-card/s-wallet-card.vue | 110 ++
sheep/config/index.js | 18 +
sheep/config/zIndex.js | 20 +
sheep/helper/digit.js | 168 +++
sheep/helper/index.js | 708 +++++++++
sheep/helper/test.js | 285 ++++
sheep/helper/throttle.js | 31 +
sheep/helper/tools.js | 67 +
sheep/helper/utils.js | 168 +++
sheep/hooks/useApp.js | 0
sheep/hooks/useGoods.js | 137 ++
sheep/hooks/useModal.js | 128 ++
sheep/index.js | 53 +
sheep/libs/luch-request/adapters/index.js | 101 ++
.../luch-request/core/InterceptorManager.js | 50 +
sheep/libs/luch-request/core/Request.js | 198 +++
sheep/libs/luch-request/core/buildFullPath.js | 20 +
sheep/libs/luch-request/core/defaults.js | 29 +
.../libs/luch-request/core/dispatchRequest.js | 3 +
sheep/libs/luch-request/core/mergeConfig.js | 103 ++
sheep/libs/luch-request/core/settle.js | 16 +
sheep/libs/luch-request/helpers/buildURL.js | 69 +
.../libs/luch-request/helpers/combineURLs.js | 14 +
.../luch-request/helpers/isAbsoluteURL.js | 14 +
sheep/libs/luch-request/index.d.ts | 116 ++
sheep/libs/luch-request/index.js | 3 +
sheep/libs/luch-request/utils.js | 131 ++
sheep/libs/luch-request/utils/clone.js | 260 ++++
sheep/libs/permission.js | 246 +++
sheep/libs/sdk-h5-weixin.js | 186 +++
sheep/platform/index.js | 175 +++
sheep/platform/pay.js | 239 +++
sheep/platform/provider/apple/app.js | 35 +
sheep/platform/provider/apple/index.js | 9 +
sheep/platform/provider/wechat/index.js | 15 +
sheep/platform/provider/wechat/miniProgram.js | 223 +++
.../provider/wechat/officialAccount.js | 112 ++
.../platform/provider/wechat/openPlatform.js | 41 +
sheep/platform/share.js | 182 +++
sheep/request/index.js | 205 +++
sheep/router/index.js | 192 +++
sheep/router/utils/strip-json-comments.js | 79 +
sheep/router/utils/uni-read-pages-v3.js | 103 ++
sheep/scss/_main.scss | 354 +++++
sheep/scss/_mixins.scss | 61 +
sheep/scss/_tools.scss | 286 ++++
sheep/scss/_var.scss | 162 ++
sheep/scss/font/OPPOSANS-M-subfont.ttf | Bin 0 -> 9832 bytes
sheep/scss/icon/_coloricon.scss | 1340 +++++++++++++++++
sheep/scss/icon/_icon.scss | 181 +++
sheep/scss/icon/_sheepicon.scss | 94 ++
sheep/scss/icon/_style.scss | 43 +
sheep/scss/index.scss | 27 +
sheep/scss/style/_avatar.scss | 0
sheep/scss/style/_background.scss | 204 +++
sheep/scss/style/_border.scss | 140 ++
sheep/scss/style/_button.scss | 87 ++
sheep/scss/style/_card.scss | 353 +++++
sheep/scss/style/_code.scss | 55 +
sheep/scss/style/_flex.scss | 79 +
sheep/scss/style/_form.scss | 121 ++
sheep/scss/style/_grid.scss | 103 ++
sheep/scss/style/_markdown.scss | 62 +
sheep/scss/style/_menu.scss | 54 +
sheep/scss/style/_shadow.scss | 90 ++
sheep/scss/style/_table.scss | 133 ++
sheep/scss/style/_tag.scss | 0
sheep/scss/style/_text.scss | 104 ++
sheep/scss/theme/_dark.scss | 39 +
sheep/scss/theme/_light.scss | 39 +
sheep/scss/theme/_style.scss | 68 +
sheep/scss/ui.scss | 19 +
sheep/store/app.js | 96 ++
sheep/store/cart.js | 108 ++
sheep/store/index.js | 20 +
sheep/store/modal.js | 29 +
sheep/store/sys.js | 32 +
sheep/store/user.js | 158 ++
sheep/ui/su-coupon/su-coupon.vue | 315 ++++
.../ui/su-data-checkbox/su-data-checkbox.vue | 894 +++++++++++
sheep/ui/su-dialog/su-dialog.vue | 269 ++++
sheep/ui/su-fixed/su-fixed.vue | 217 +++
sheep/ui/su-image/su-image.vue | 129 ++
sheep/ui/su-inner-navbar/su-inner-navbar.vue | 365 +++++
sheep/ui/su-navbar/su-navbar.vue | 477 ++++++
sheep/ui/su-notice-bar/su-notice-bar.vue | 472 ++++++
sheep/ui/su-number-box/su-number-box.vue | 290 ++++
sheep/ui/su-parse/node.vue | 536 +++++++
sheep/ui/su-parse/node/node.vue | 619 ++++++++
sheep/ui/su-parse/parser.js | 1260 ++++++++++++++++
sheep/ui/su-parse/su-parse.vue | 430 ++++++
sheep/ui/su-popover/su-popover.vue | 314 ++++
sheep/ui/su-popup/keypress.js | 45 +
sheep/ui/su-popup/su-popup.vue | 589 ++++++++
sheep/ui/su-progress/su-progress.vue | 200 +++
sheep/ui/su-radio/su-radio.vue | 301 ++++
.../ui/su-region-picker/su-region-picker.vue | 246 +++
sheep/ui/su-status-bar/su-status-bar.vue | 16 +
sheep/ui/su-sticky/su-sticky.vue | 264 ++++
sheep/ui/su-subline/su-subline.vue | 42 +
sheep/ui/su-swiper/su-swiper.vue | 495 ++++++
sheep/ui/su-switch/su-switch.vue | 100 ++
sheep/ui/su-tab-item/su-tab-item.vue | 169 +++
sheep/ui/su-tab/su-tab.vue | 474 ++++++
sheep/ui/su-tabbar-item/su-tabbar-item.vue | 233 +++
sheep/ui/su-tabbar/su-tabbar.vue | 226 +++
sheep/ui/su-tabs-item/props.js | 3 +
sheep/ui/su-tabs-item/su-tabs-item.vue | 26 +
sheep/ui/su-tabs/su-tabs.vue | 434 ++++++
sheep/ui/su-time-line/su-time-line.vue | 37 +
.../ui/su-timeline-item/su-timeline-item.vue | 76 +
sheep/ui/su-toolbar/su-toolbar.vue | 129 ++
sheep/ui/su-video/su-video.vue | 212 +++
sheep/url/index.js | 199 +++
sheep/validate/form.js | 230 +++
static/activity-left.png | Bin 0 -> 440 bytes
static/activity-right.png | Bin 0 -> 1400 bytes
static/cart-empty.png | Bin 0 -> 5933 bytes
static/collect-empty.png | Bin 0 -> 5849 bytes
static/comment-empty.png | Bin 0 -> 5603 bytes
static/coupon-empty.png | Bin 0 -> 5245 bytes
static/data-empty.png | Bin 0 -> 5053 bytes
static/goods-empty.png | Bin 0 -> 3192 bytes
static/internet-empty.png | Bin 0 -> 7003 bytes
static/order-empty.png | Bin 0 -> 4406 bytes
static/soldout-empty.png | Bin 0 -> 7012 bytes
uni.scss | 76 +
uni_modules/uni-badge/changelog.md | 29 +
.../components/uni-badge/uni-badge.vue | 268 ++++
uni_modules/uni-badge/package.json | 88 ++
uni_modules/uni-badge/readme.md | 10 +
uni_modules/uni-card/changelog.md | 26 +
.../uni-card/components/uni-card/uni-card.vue | 270 ++++
uni_modules/uni-card/package.json | 90 ++
uni_modules/uni-card/readme.md | 12 +
uni_modules/uni-collapse/changelog.md | 36 +
.../uni-collapse-item/uni-collapse-item.vue | 402 +++++
.../components/uni-collapse/uni-collapse.vue | 147 ++
uni_modules/uni-collapse/package.json | 89 ++
uni_modules/uni-collapse/readme.md | 12 +
uni_modules/uni-combox/changelog.md | 15 +
.../components/uni-combox/uni-combox.vue | 275 ++++
uni_modules/uni-combox/package.json | 90 ++
uni_modules/uni-combox/readme.md | 11 +
uni_modules/uni-countdown/changelog.md | 24 +
.../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 | 271 ++++
uni_modules/uni-countdown/package.json | 86 ++
uni_modules/uni-countdown/readme.md | 10 +
uni_modules/uni-data-checkbox/changelog.md | 41 +
.../uni-data-checkbox/uni-data-checkbox.vue | 817 ++++++++++
uni_modules/uni-data-checkbox/package.json | 87 ++
uni_modules/uni-data-checkbox/readme.md | 18 +
uni_modules/uni-data-picker/changelog.md | 58 +
.../components/uni-data-picker/keypress.js | 45 +
.../uni-data-picker/uni-data-picker.vue | 539 +++++++
.../uni-data-pickerview/uni-data-picker.js | 563 +++++++
.../uni-data-pickerview.vue | 333 ++++
uni_modules/uni-data-picker/package.json | 92 ++
uni_modules/uni-data-picker/readme.md | 22 +
uni_modules/uni-dateformat/changelog.md | 10 +
.../components/uni-dateformat/date-format.js | 200 +++
.../uni-dateformat/uni-dateformat.vue | 88 ++
uni_modules/uni-dateformat/package.json | 88 ++
uni_modules/uni-dateformat/readme.md | 11 +
uni_modules/uni-datetime-picker/changelog.md | 89 ++
.../uni-datetime-picker/calendar-item.vue | 185 +++
.../uni-datetime-picker/calendar.js | 546 +++++++
.../uni-datetime-picker/calendar.vue | 898 +++++++++++
.../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 | 927 ++++++++++++
.../uni-datetime-picker.vue | 997 ++++++++++++
.../components/uni-datetime-picker/util.js | 410 +++++
uni_modules/uni-datetime-picker/package.json | 90 ++
uni_modules/uni-datetime-picker/readme.md | 21 +
uni_modules/uni-drawer/changelog.md | 13 +
.../components/uni-drawer/keypress.js | 45 +
.../components/uni-drawer/uni-drawer.vue | 183 +++
uni_modules/uni-drawer/package.json | 87 ++
uni_modules/uni-drawer/readme.md | 10 +
uni_modules/uni-easyinput/changelog.md | 41 +
.../components/uni-easyinput/common.js | 54 +
.../uni-easyinput/uni-easyinput.vue | 538 +++++++
uni_modules/uni-easyinput/package.json | 90 ++
uni_modules/uni-easyinput/readme.md | 11 +
uni_modules/uni-fab/changelog.md | 17 +
.../uni-fab/components/uni-fab/uni-fab.vue | 529 +++++++
uni_modules/uni-fab/package.json | 87 ++
uni_modules/uni-fab/readme.md | 9 +
uni_modules/uni-fav/changelog.md | 19 +
.../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 | 161 ++
uni_modules/uni-fav/package.json | 89 ++
uni_modules/uni-fav/readme.md | 10 +
uni_modules/uni-forms/changelog.md | 60 +
.../uni-forms-item/uni-forms-item.vue | 536 +++++++
.../components/uni-forms/uni-forms.vue | 472 ++++++
.../components/uni-forms/validate.js | 486 ++++++
uni_modules/uni-forms/package.json | 90 ++
uni_modules/uni-forms/readme.md | 23 +
uni_modules/uni-goods-nav/changelog.md | 18 +
.../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 | 229 +++
uni_modules/uni-goods-nav/package.json | 88 ++
uni_modules/uni-goods-nav/readme.md | 10 +
uni_modules/uni-grid/changelog.md | 13 +
.../uni-grid-item/uni-grid-item.vue | 140 ++
.../uni-grid/components/uni-grid/uni-grid.vue | 142 ++
uni_modules/uni-grid/package.json | 86 ++
uni_modules/uni-grid/readme.md | 11 +
uni_modules/uni-group/changelog.md | 16 +
.../components/uni-group/uni-group.vue | 134 ++
uni_modules/uni-group/package.json | 87 ++
uni_modules/uni-group/readme.md | 9 +
uni_modules/uni-icons/changelog.md | 22 +
.../uni-icons/components/uni-icons/icons.js | 1169 ++++++++++++++
.../components/uni-icons/uni-icons.vue | 96 ++
.../uni-icons/components/uni-icons/uni.ttf | Bin 0 -> 26164 bytes
.../components/uni-icons/uniicons.css | 663 ++++++++
.../components/uni-icons/uniicons.ttf | Bin 0 -> 35760 bytes
uni_modules/uni-icons/package.json | 86 ++
uni_modules/uni-icons/readme.md | 8 +
uni_modules/uni-link/changelog.md | 17 +
.../uni-link/components/uni-link/uni-link.vue | 128 ++
uni_modules/uni-link/package.json | 87 ++
uni_modules/uni-link/readme.md | 11 +
uni_modules/uni-list/changelog.md | 20 +
.../components/uni-list-ad/uni-list-ad.vue | 107 ++
.../uni-list-chat/uni-list-chat.scss | 58 +
.../uni-list-chat/uni-list-chat.vue | 538 +++++++
.../uni-list-item/uni-list-item.vue | 473 ++++++
.../uni-list/components/uni-list/uni-list.vue | 108 ++
.../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 | 346 +++++
uni_modules/uni-load-more/changelog.md | 19 +
.../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 | 399 +++++
uni_modules/uni-load-more/package.json | 86 ++
uni_modules/uni-load-more/readme.md | 14 +
uni_modules/uni-nav-bar/changelog.md | 39 +
.../components/uni-nav-bar/uni-nav-bar.vue | 348 +++++
.../components/uni-nav-bar/uni-status-bar.vue | 27 +
uni_modules/uni-nav-bar/package.json | 89 ++
uni_modules/uni-nav-bar/readme.md | 15 +
uni_modules/uni-notice-bar/changelog.md | 16 +
.../uni-notice-bar/uni-notice-bar.vue | 395 +++++
uni_modules/uni-notice-bar/package.json | 90 ++
uni_modules/uni-notice-bar/readme.md | 13 +
uni_modules/uni-number-box/changelog.md | 25 +
.../uni-number-box/uni-number-box.vue | 220 +++
uni_modules/uni-number-box/package.json | 85 ++
uni_modules/uni-number-box/readme.md | 13 +
uni_modules/uni-rate/changelog.md | 25 +
.../uni-rate/components/uni-rate/uni-rate.vue | 361 +++++
uni_modules/uni-rate/package.json | 88 ++
uni_modules/uni-rate/readme.md | 12 +
uni_modules/uni-row/changelog.md | 10 +
.../uni-row/components/uni-col/uni-col.vue | 317 ++++
.../uni-row/components/uni-row/uni-row.vue | 190 +++
uni_modules/uni-row/package.json | 87 ++
uni_modules/uni-row/readme.md | 10 +
uni_modules/uni-scss/changelog.md | 8 +
uni_modules/uni-scss/index.scss | 1 +
uni_modules/uni-scss/manifest.json | 95 ++
uni_modules/uni-scss/package.json | 82 +
uni_modules/uni-scss/readme.md | 4 +
uni_modules/uni-scss/styles/index.scss | 7 +
.../uni-scss/styles/setting/_border.scss | 3 +
.../uni-scss/styles/setting/_color.scss | 66 +
.../uni-scss/styles/setting/_radius.scss | 55 +
.../uni-scss/styles/setting/_space.scss | 56 +
.../uni-scss/styles/setting/_styles.scss | 167 ++
.../uni-scss/styles/setting/_text.scss | 24 +
.../uni-scss/styles/setting/_variables.scss | 146 ++
.../uni-scss/styles/tools/functions.scss | 19 +
uni_modules/uni-scss/theme.scss | 31 +
uni_modules/uni-scss/variables.scss | 62 +
uni_modules/uni-search-bar/changelog.md | 33 +
.../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 | 298 ++++
uni_modules/uni-search-bar/package.json | 89 ++
uni_modules/uni-search-bar/readme.md | 14 +
.../uni-segmented-control/changelog.md | 9 +
.../uni-segmented-control.vue | 145 ++
.../uni-segmented-control/package.json | 87 ++
uni_modules/uni-segmented-control/readme.md | 13 +
uni_modules/uni-steps/changelog.md | 16 +
.../components/uni-steps/uni-steps.vue | 269 ++++
uni_modules/uni-steps/package.json | 89 ++
uni_modules/uni-steps/readme.md | 13 +
uni_modules/uni-swiper-dot/changelog.md | 12 +
.../uni-swiper-dot/uni-swiper-dot.vue | 218 +++
uni_modules/uni-swiper-dot/package.json | 87 ++
uni_modules/uni-swiper-dot/readme.md | 11 +
uni_modules/uni-tag/changelog.md | 21 +
.../uni-tag/components/uni-tag/uni-tag.vue | 252 ++++
uni_modules/uni-tag/package.json | 87 ++
uni_modules/uni-tag/readme.md | 13 +
uni_modules/uni-title/changelog.md | 10 +
.../components/uni-title/uni-title.vue | 171 +++
uni_modules/uni-title/package.json | 88 ++
uni_modules/uni-title/readme.md | 14 +
uni_modules/uni-tooltip/changelog.md | 10 +
.../components/uni-tooltip/uni-tooltip.vue | 68 +
uni_modules/uni-tooltip/package.json | 83 +
uni_modules/uni-tooltip/readme.md | 8 +
uni_modules/uni-transition/changelog.md | 20 +
.../uni-transition/createAnimation.js | 128 ++
.../uni-transition/uni-transition.vue | 299 ++++
uni_modules/uni-transition/package.json | 87 ++
uni_modules/uni-transition/readme.md | 11 +
vite.config.js | 32 +
525 files changed, 80922 insertions(+)
create mode 100644 .env.example
create mode 100644 .gitignore
create mode 100644 .prettierignore
create mode 100644 .prettierrc
create mode 100644 App.vue
create mode 100644 androidPrivacy.json
create mode 100644 index.html
create mode 100644 jsconfig.json
create mode 100644 main.js
create mode 100644 manifest.json
create mode 100644 package.json
create mode 100644 pages.json
create mode 100644 pages/activity/groupon/detail.vue
create mode 100644 pages/activity/groupon/list.vue
create mode 100644 pages/activity/groupon/order.vue
create mode 100644 pages/activity/index.vue
create mode 100644 pages/activity/seckill/list.vue
create mode 100644 pages/app/score-shop.vue
create mode 100644 pages/app/sign.vue
create mode 100644 pages/chat/components/goods.vue
create mode 100644 pages/chat/components/order.vue
create mode 100644 pages/chat/components/select-popup.vue
create mode 100644 pages/chat/emoji.js
create mode 100644 pages/chat/index.vue
create mode 100644 pages/chat/socket.js
create mode 100644 pages/commission/apply.vue
create mode 100644 pages/commission/components/account-info.vue
create mode 100644 pages/commission/components/commission-auth.vue
create mode 100644 pages/commission/components/commission-condition.vue
create mode 100644 pages/commission/components/commission-info.vue
create mode 100644 pages/commission/components/commission-log.vue
create mode 100644 pages/commission/components/commission-menu.vue
create mode 100644 pages/commission/goods.vue
create mode 100644 pages/commission/index.vue
create mode 100644 pages/commission/order.vue
create mode 100644 pages/commission/share-log.vue
create mode 100644 pages/commission/team.vue
create mode 100644 pages/coupon/detail.vue
create mode 100644 pages/coupon/list.vue
create mode 100644 pages/goods/comment/add.vue
create mode 100644 pages/goods/comment/list.vue
create mode 100644 pages/goods/components/detail/comment-item.vue
create mode 100644 pages/goods/components/detail/detail-activity-tip.vue
create mode 100644 pages/goods/components/detail/detail-cell-params.vue
create mode 100644 pages/goods/components/detail/detail-cell-service.vue
create mode 100644 pages/goods/components/detail/detail-cell-sku.vue
create mode 100644 pages/goods/components/detail/detail-cell.vue
create mode 100644 pages/goods/components/detail/detail-comment-card.vue
create mode 100644 pages/goods/components/detail/detail-content-card.vue
create mode 100644 pages/goods/components/detail/detail-navbar.vue
create mode 100644 pages/goods/components/detail/detail-progress.vue
create mode 100644 pages/goods/components/detail/detail-skeleton.vue
create mode 100644 pages/goods/components/detail/detail-tabbar.vue
create mode 100644 pages/goods/components/groupon/groupon-card-list.vue
create mode 100644 pages/goods/components/list/list-goods-card.vue
create mode 100644 pages/goods/components/list/list-navbar.vue
create mode 100644 pages/goods/groupon.vue
create mode 100644 pages/goods/index.vue
create mode 100644 pages/goods/list.vue
create mode 100644 pages/goods/score.vue
create mode 100644 pages/goods/seckill.vue
create mode 100644 pages/index/cart.vue
create mode 100644 pages/index/category.vue
create mode 100644 pages/index/components/first-one.vue
create mode 100644 pages/index/components/first-two.vue
create mode 100644 pages/index/components/second-one.vue
create mode 100644 pages/index/components/third-one.vue
create mode 100644 pages/index/index.vue
create mode 100644 pages/index/login.vue
create mode 100644 pages/index/page.vue
create mode 100644 pages/index/search.vue
create mode 100644 pages/index/user.vue
create mode 100644 pages/order/aftersale/apply.vue
create mode 100644 pages/order/aftersale/detail.vue
create mode 100644 pages/order/aftersale/list.vue
create mode 100644 pages/order/aftersale/log.vue
create mode 100644 pages/order/confirm.vue
create mode 100644 pages/order/detail.vue
create mode 100644 pages/order/express/list.vue
create mode 100644 pages/order/express/log.vue
create mode 100644 pages/order/invoice.vue
create mode 100644 pages/order/list.vue
create mode 100644 pages/pay/components/account-info-modal.vue
create mode 100644 pages/pay/components/account-type-select.vue
create mode 100644 pages/pay/components/add-alipay.vue
create mode 100644 pages/pay/components/transfter-balance.vue
create mode 100644 pages/pay/index.vue
create mode 100644 pages/pay/recharge-log.vue
create mode 100644 pages/pay/recharge.vue
create mode 100644 pages/pay/result.vue
create mode 100644 pages/pay/withdraw-log.vue
create mode 100644 pages/pay/withdraw.vue
create mode 100644 pages/public/error.vue
create mode 100644 pages/public/faq.vue
create mode 100644 pages/public/feedback.vue
create mode 100644 pages/public/richtext.vue
create mode 100644 pages/public/setting.vue
create mode 100644 pages/public/webview.vue
create mode 100644 pages/user/address/edit.vue
create mode 100644 pages/user/address/list.vue
create mode 100644 pages/user/goods-collect.vue
create mode 100644 pages/user/goods-log.vue
create mode 100644 pages/user/info.vue
create mode 100644 pages/user/invoice/edit.vue
create mode 100644 pages/user/invoice/list.vue
create mode 100644 pages/user/set.vue
create mode 100644 pages/user/wallet/commission.vue
create mode 100644 pages/user/wallet/money.vue
create mode 100644 pages/user/wallet/score.vue
create mode 100644 sheep/api/activity.js
create mode 100644 sheep/api/app.js
create mode 100644 sheep/api/cart.js
create mode 100644 sheep/api/category.js
create mode 100644 sheep/api/chat.js
create mode 100644 sheep/api/commission.js
create mode 100644 sheep/api/coupon.js
create mode 100644 sheep/api/data.js
create mode 100644 sheep/api/goods.js
create mode 100644 sheep/api/index.js
create mode 100644 sheep/api/order.js
create mode 100644 sheep/api/pay.js
create mode 100644 sheep/api/third.js
create mode 100644 sheep/api/trade.js
create mode 100644 sheep/api/user.js
create mode 100644 sheep/components/s-activity-pop/s-activity-pop.vue
create mode 100644 sheep/components/s-address-item/s-address-item.vue
create mode 100644 sheep/components/s-auth-modal/components/account-login.vue
create mode 100644 sheep/components/s-auth-modal/components/change-mobile.vue
create mode 100644 sheep/components/s-auth-modal/components/change-password.vue
create mode 100644 sheep/components/s-auth-modal/components/change-username.vue
create mode 100644 sheep/components/s-auth-modal/components/reset-password.vue
create mode 100644 sheep/components/s-auth-modal/components/sms-login.vue
create mode 100644 sheep/components/s-auth-modal/components/sms-register.vue
create mode 100644 sheep/components/s-auth-modal/index.scss
create mode 100644 sheep/components/s-auth-modal/s-auth-modal.vue
create mode 100644 sheep/components/s-block-item/s-block-item.vue
create mode 100644 sheep/components/s-block/s-block.vue
create mode 100644 sheep/components/s-coupon-block/s-coupon-block.vue
create mode 100644 sheep/components/s-coupon-card/s-coupon-card.vue
create mode 100644 sheep/components/s-coupon-get/s-coupon-get.vue
create mode 100644 sheep/components/s-coupon-list/s-coupon-list.vue
create mode 100644 sheep/components/s-coupon-select/s-coupon-select.vue
create mode 100644 sheep/components/s-custom-navbar/components/navbar-item.vue
create mode 100644 sheep/components/s-custom-navbar/components/navbar.vue
create mode 100644 sheep/components/s-custom-navbar/s-custom-navbar.vue
create mode 100644 sheep/components/s-discount-list/s-discount-list.vue
create mode 100644 sheep/components/s-empty/s-empty.vue
create mode 100644 sheep/components/s-float-menu/s-float-menu.vue
create mode 100644 sheep/components/s-goods-card/s-goods-card.vue
create mode 100644 sheep/components/s-goods-column/s-goods-column.vue
create mode 100644 sheep/components/s-goods-item/s-goods-item.vue
create mode 100644 sheep/components/s-goods-scroll/s-goods-scroll.vue
create mode 100644 sheep/components/s-goods-shelves/s-goods-shelves.vue
create mode 100644 sheep/components/s-groupon-block/s-groupon-block.vue
create mode 100644 sheep/components/s-image-banner/s-image-banner.vue
create mode 100644 sheep/components/s-image-block/s-image-block.vue
create mode 100644 sheep/components/s-image-cube/s-image-cube.vue
create mode 100644 sheep/components/s-invoice-item/s-invoice-item.vue
create mode 100644 sheep/components/s-layout/s-layout.vue
create mode 100644 sheep/components/s-line-block/s-line-block.vue
create mode 100644 sheep/components/s-menu-button/s-menu-button.vue
create mode 100644 sheep/components/s-menu-grid/s-menu-grid.vue
create mode 100644 sheep/components/s-menu-list/s-menu-list.vue
create mode 100644 sheep/components/s-menu-tools/s-menu-tools.vue
create mode 100644 sheep/components/s-notice-block/s-notice-block.vue
create mode 100644 sheep/components/s-order-card/s-order-card.vue
create mode 100644 sheep/components/s-popup-image/s-popup-image.vue
create mode 100644 sheep/components/s-richtext-block/s-richtext-block.vue
create mode 100644 sheep/components/s-score-block/s-score-block.vue
create mode 100644 sheep/components/s-score-card/s-score-card.vue
create mode 100644 sheep/components/s-search-block/s-search-block.vue
create mode 100644 sheep/components/s-seckill-block/s-seckill-block.vue
create mode 100644 sheep/components/s-select-groupon-sku/s-select-groupon-sku.vue
create mode 100644 sheep/components/s-select-seckill-sku/s-select-seckill-sku.vue
create mode 100644 sheep/components/s-select-sku/s-select-sku.vue
create mode 100644 sheep/components/s-share-modal/canvas-poster/index.vue
create mode 100644 sheep/components/s-share-modal/canvas-poster/poster/goods.js
create mode 100644 sheep/components/s-share-modal/canvas-poster/poster/groupon.js
create mode 100644 sheep/components/s-share-modal/canvas-poster/poster/index.js
create mode 100644 sheep/components/s-share-modal/canvas-poster/poster/user.js
create mode 100644 sheep/components/s-share-modal/canvas-poster/useCanvas.js
create mode 100644 sheep/components/s-share-modal/s-share-modal.vue
create mode 100644 sheep/components/s-statusbar/s-statusbar.vue
create mode 100644 sheep/components/s-tabbar/s-tabbar.vue
create mode 100644 sheep/components/s-title-block/s-title-block.vue
create mode 100644 sheep/components/s-uploader/choose-and-upload-file.js
create mode 100644 sheep/components/s-uploader/s-uploader.vue
create mode 100644 sheep/components/s-uploader/upload-file.vue
create mode 100644 sheep/components/s-uploader/upload-image.vue
create mode 100644 sheep/components/s-uploader/utils.js
create mode 100644 sheep/components/s-user-card/s-user-card.vue
create mode 100644 sheep/components/s-video-block/s-video-block.vue
create mode 100644 sheep/components/s-wallet-card/s-wallet-card.vue
create mode 100644 sheep/config/index.js
create mode 100644 sheep/config/zIndex.js
create mode 100644 sheep/helper/digit.js
create mode 100644 sheep/helper/index.js
create mode 100644 sheep/helper/test.js
create mode 100644 sheep/helper/throttle.js
create mode 100644 sheep/helper/tools.js
create mode 100644 sheep/helper/utils.js
create mode 100644 sheep/hooks/useApp.js
create mode 100644 sheep/hooks/useGoods.js
create mode 100644 sheep/hooks/useModal.js
create mode 100644 sheep/index.js
create mode 100644 sheep/libs/luch-request/adapters/index.js
create mode 100644 sheep/libs/luch-request/core/InterceptorManager.js
create mode 100644 sheep/libs/luch-request/core/Request.js
create mode 100644 sheep/libs/luch-request/core/buildFullPath.js
create mode 100644 sheep/libs/luch-request/core/defaults.js
create mode 100644 sheep/libs/luch-request/core/dispatchRequest.js
create mode 100644 sheep/libs/luch-request/core/mergeConfig.js
create mode 100644 sheep/libs/luch-request/core/settle.js
create mode 100644 sheep/libs/luch-request/helpers/buildURL.js
create mode 100644 sheep/libs/luch-request/helpers/combineURLs.js
create mode 100644 sheep/libs/luch-request/helpers/isAbsoluteURL.js
create mode 100644 sheep/libs/luch-request/index.d.ts
create mode 100644 sheep/libs/luch-request/index.js
create mode 100644 sheep/libs/luch-request/utils.js
create mode 100644 sheep/libs/luch-request/utils/clone.js
create mode 100644 sheep/libs/permission.js
create mode 100644 sheep/libs/sdk-h5-weixin.js
create mode 100644 sheep/platform/index.js
create mode 100644 sheep/platform/pay.js
create mode 100644 sheep/platform/provider/apple/app.js
create mode 100644 sheep/platform/provider/apple/index.js
create mode 100644 sheep/platform/provider/wechat/index.js
create mode 100644 sheep/platform/provider/wechat/miniProgram.js
create mode 100644 sheep/platform/provider/wechat/officialAccount.js
create mode 100644 sheep/platform/provider/wechat/openPlatform.js
create mode 100644 sheep/platform/share.js
create mode 100644 sheep/request/index.js
create mode 100644 sheep/router/index.js
create mode 100644 sheep/router/utils/strip-json-comments.js
create mode 100644 sheep/router/utils/uni-read-pages-v3.js
create mode 100644 sheep/scss/_main.scss
create mode 100644 sheep/scss/_mixins.scss
create mode 100644 sheep/scss/_tools.scss
create mode 100644 sheep/scss/_var.scss
create mode 100644 sheep/scss/font/OPPOSANS-M-subfont.ttf
create mode 100644 sheep/scss/icon/_coloricon.scss
create mode 100644 sheep/scss/icon/_icon.scss
create mode 100644 sheep/scss/icon/_sheepicon.scss
create mode 100644 sheep/scss/icon/_style.scss
create mode 100644 sheep/scss/index.scss
create mode 100644 sheep/scss/style/_avatar.scss
create mode 100644 sheep/scss/style/_background.scss
create mode 100644 sheep/scss/style/_border.scss
create mode 100644 sheep/scss/style/_button.scss
create mode 100644 sheep/scss/style/_card.scss
create mode 100644 sheep/scss/style/_code.scss
create mode 100644 sheep/scss/style/_flex.scss
create mode 100644 sheep/scss/style/_form.scss
create mode 100644 sheep/scss/style/_grid.scss
create mode 100644 sheep/scss/style/_markdown.scss
create mode 100644 sheep/scss/style/_menu.scss
create mode 100644 sheep/scss/style/_shadow.scss
create mode 100644 sheep/scss/style/_table.scss
create mode 100644 sheep/scss/style/_tag.scss
create mode 100644 sheep/scss/style/_text.scss
create mode 100644 sheep/scss/theme/_dark.scss
create mode 100644 sheep/scss/theme/_light.scss
create mode 100644 sheep/scss/theme/_style.scss
create mode 100644 sheep/scss/ui.scss
create mode 100644 sheep/store/app.js
create mode 100644 sheep/store/cart.js
create mode 100644 sheep/store/index.js
create mode 100644 sheep/store/modal.js
create mode 100644 sheep/store/sys.js
create mode 100644 sheep/store/user.js
create mode 100644 sheep/ui/su-coupon/su-coupon.vue
create mode 100644 sheep/ui/su-data-checkbox/su-data-checkbox.vue
create mode 100644 sheep/ui/su-dialog/su-dialog.vue
create mode 100644 sheep/ui/su-fixed/su-fixed.vue
create mode 100644 sheep/ui/su-image/su-image.vue
create mode 100644 sheep/ui/su-inner-navbar/su-inner-navbar.vue
create mode 100644 sheep/ui/su-navbar/su-navbar.vue
create mode 100644 sheep/ui/su-notice-bar/su-notice-bar.vue
create mode 100644 sheep/ui/su-number-box/su-number-box.vue
create mode 100644 sheep/ui/su-parse/node.vue
create mode 100644 sheep/ui/su-parse/node/node.vue
create mode 100644 sheep/ui/su-parse/parser.js
create mode 100644 sheep/ui/su-parse/su-parse.vue
create mode 100644 sheep/ui/su-popover/su-popover.vue
create mode 100644 sheep/ui/su-popup/keypress.js
create mode 100644 sheep/ui/su-popup/su-popup.vue
create mode 100644 sheep/ui/su-progress/su-progress.vue
create mode 100644 sheep/ui/su-radio/su-radio.vue
create mode 100644 sheep/ui/su-region-picker/su-region-picker.vue
create mode 100644 sheep/ui/su-status-bar/su-status-bar.vue
create mode 100644 sheep/ui/su-sticky/su-sticky.vue
create mode 100644 sheep/ui/su-subline/su-subline.vue
create mode 100644 sheep/ui/su-swiper/su-swiper.vue
create mode 100644 sheep/ui/su-switch/su-switch.vue
create mode 100644 sheep/ui/su-tab-item/su-tab-item.vue
create mode 100644 sheep/ui/su-tab/su-tab.vue
create mode 100644 sheep/ui/su-tabbar-item/su-tabbar-item.vue
create mode 100644 sheep/ui/su-tabbar/su-tabbar.vue
create mode 100644 sheep/ui/su-tabs-item/props.js
create mode 100644 sheep/ui/su-tabs-item/su-tabs-item.vue
create mode 100644 sheep/ui/su-tabs/su-tabs.vue
create mode 100644 sheep/ui/su-time-line/su-time-line.vue
create mode 100644 sheep/ui/su-timeline-item/su-timeline-item.vue
create mode 100644 sheep/ui/su-toolbar/su-toolbar.vue
create mode 100644 sheep/ui/su-video/su-video.vue
create mode 100644 sheep/url/index.js
create mode 100644 sheep/validate/form.js
create mode 100644 static/activity-left.png
create mode 100644 static/activity-right.png
create mode 100644 static/cart-empty.png
create mode 100644 static/collect-empty.png
create mode 100644 static/comment-empty.png
create mode 100644 static/coupon-empty.png
create mode 100644 static/data-empty.png
create mode 100644 static/goods-empty.png
create mode 100644 static/internet-empty.png
create mode 100644 static/order-empty.png
create mode 100644 static/soldout-empty.png
create mode 100644 uni.scss
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-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-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/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/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-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/components/uni-icons/uniicons.css
create mode 100644 uni_modules/uni-icons/components/uni-icons/uniicons.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-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-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-scss/changelog.md
create mode 100644 uni_modules/uni-scss/index.scss
create mode 100644 uni_modules/uni-scss/manifest.json
create mode 100644 uni_modules/uni-scss/package.json
create mode 100644 uni_modules/uni-scss/readme.md
create mode 100644 uni_modules/uni-scss/styles/index.scss
create mode 100644 uni_modules/uni-scss/styles/setting/_border.scss
create mode 100644 uni_modules/uni-scss/styles/setting/_color.scss
create mode 100644 uni_modules/uni-scss/styles/setting/_radius.scss
create mode 100644 uni_modules/uni-scss/styles/setting/_space.scss
create mode 100644 uni_modules/uni-scss/styles/setting/_styles.scss
create mode 100644 uni_modules/uni-scss/styles/setting/_text.scss
create mode 100644 uni_modules/uni-scss/styles/setting/_variables.scss
create mode 100644 uni_modules/uni-scss/styles/tools/functions.scss
create mode 100644 uni_modules/uni-scss/theme.scss
create mode 100644 uni_modules/uni-scss/variables.scss
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-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-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-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-tooltip/changelog.md
create mode 100644 uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue
create mode 100644 uni_modules/uni-tooltip/package.json
create mode 100644 uni_modules/uni-tooltip/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 vite.config.js
diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000..1df3bc7d
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,14 @@
+# 正式环境接口域名
+SHOPRO_BASE_URL = https://api.shopro.sheepjs.com
+
+# 开发环境接口域名
+SHOPRO_DEV_BASE_URL = https://dev.shopro.sheepjs.com
+
+# 开发环境运行端口
+SHOPRO_DEV_PORT = 3000
+
+# 接口地址前缀
+SHOPRO_API_PATH = /shop/api/
+
+# 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀
+SHOPRO_STATIC_URL =
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..0402c0df
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+/unpackage/*
+/node_modules/*
+/.idea/*
+deploy.sh
+/.hbuilderx/
+/.vscode/
+**/.DS_Store
+.env
+yarn.lock
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..57741cef
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,7 @@
+/unpackage/*
+/node_modules/**
+/uni_modules/**
+/public/*
+/sheep/ui/su-parse/*
+**/*.svg
+**/*.sh
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000..fe0a3f94
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "printWidth": 100,
+ "semi": true,
+ "vueIndentScriptAndStyle": true,
+ "singleQuote": true,
+ "trailingComma": "all",
+ "proseWrap": "never",
+ "htmlWhitespaceSensitivity": "strict",
+ "endOfLine": "auto"
+}
diff --git a/App.vue b/App.vue
new file mode 100644
index 00000000..2865ab52
--- /dev/null
+++ b/App.vue
@@ -0,0 +1,34 @@
+
+
+
diff --git a/androidPrivacy.json b/androidPrivacy.json
new file mode 100644
index 00000000..0d726ca9
--- /dev/null
+++ b/androidPrivacy.json
@@ -0,0 +1,3 @@
+{
+ "prompt" : "template"
+}
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..2269a693
--- /dev/null
+++ b/index.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 00000000..b1968eea
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "jsx": "preserve",
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./*"]
+ }
+ }
+}
diff --git a/main.js b/main.js
new file mode 100644
index 00000000..2680ac67
--- /dev/null
+++ b/main.js
@@ -0,0 +1,15 @@
+import App from './App';
+import { createSSRApp } from 'vue';
+import { setupPinia } from './sheep/store';
+
+
+export function createApp() {
+
+ const app = createSSRApp(App);
+
+ setupPinia(app);
+
+ return {
+ app,
+ };
+}
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 00000000..53d6cf1f
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,224 @@
+{
+ "name" : "星品购",
+ "appid" : "__UNI__082C0BA",
+ "description" : "应用描述",
+ "versionName" : "1.0.7",
+ "versionCode" : 107,
+ "transformPx" : false,
+ "app-plus" : {
+ "usingComponents" : true,
+ "nvueCompiler" : "uni-app",
+ "nvueStyleCompiler" : "uni-app",
+ "compilerVersion" : 3,
+ "nvueLaunchMode" : "fast",
+ "splashscreen" : {
+ "alwaysShowBeforeRender" : true,
+ "waiting" : true,
+ "autoclose" : true,
+ "delay" : 0
+ },
+ "safearea" : {
+ "bottom" : {
+ "offset" : "none"
+ }
+ },
+ "modules" : {
+ "Payment" : {},
+ "Share" : {},
+ "VideoPlayer" : {},
+ "OAuth" : {}
+ },
+ "distribute" : {
+ "android" : {
+ "permissions" : [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ],
+ "minSdkVersion" : 21,
+ "schemes" : "shopro"
+ },
+ "ios" : {
+ "urlschemewhitelist" : [ "baidumap", "iosamap" ],
+ "dSYMs" : false,
+ "privacyDescription" : {
+ "NSPhotoLibraryUsageDescription" : "需要同意访问您的相册选取图片才能完善该条目",
+ "NSPhotoLibraryAddUsageDescription" : "需要同意访问您的相册才能保存该图片",
+ "NSCameraUsageDescription" : "需要同意访问您的摄像头拍摄照片才能完善该条目",
+ "NSUserTrackingUsageDescription" : "开启追踪并不会获取您在其它站点的隐私信息,该行为仅用于标识设备,保障服务安全和提升浏览体验"
+ },
+ "urltypes" : "shopro",
+ "capabilities" : {
+ "entitlements" : {
+ "com.apple.developer.associated-domains" : [ "applinks:shopro.sheepjs.com" ]
+ }
+ },
+ "idfa" : true
+ },
+ "sdkConfigs" : {
+ "speech" : {
+ "ifly" : {}
+ },
+ "ad" : {},
+ "oauth" : {
+ "apple" : {},
+ "weixin" : {
+ "appid" : "wxae7a0c156da9383b",
+ "UniversalLinks" : "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/"
+ }
+ },
+ "payment" : {
+ "weixin" : {
+ "__platform__" : [ "ios", "android" ],
+ "appid" : "wxae7a0c156da9383b",
+ "UniversalLinks" : "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/"
+ },
+ "alipay" : {
+ "__platform__" : [ "ios", "android" ]
+ }
+ },
+ "share" : {
+ "weixin" : {
+ "appid" : "wxae7a0c156da9383b",
+ "UniversalLinks" : "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/"
+ }
+ }
+ },
+ "orientation" : [ "portrait-primary" ],
+ "splashscreen" : {
+ "androidStyle" : "common",
+ "iosStyle" : "common",
+ "useOriginalMsgbox" : true
+ },
+ "icons" : {
+ "android" : {
+ "hdpi" : "unpackage/res/icons/72x72.png",
+ "xhdpi" : "unpackage/res/icons/96x96.png",
+ "xxhdpi" : "unpackage/res/icons/144x144.png",
+ "xxxhdpi" : "unpackage/res/icons/192x192.png"
+ },
+ "ios" : {
+ "appstore" : "unpackage/res/icons/1024x1024.png",
+ "ipad" : {
+ "app" : "unpackage/res/icons/76x76.png",
+ "app@2x" : "unpackage/res/icons/152x152.png",
+ "notification" : "unpackage/res/icons/20x20.png",
+ "notification@2x" : "unpackage/res/icons/40x40.png",
+ "proapp@2x" : "unpackage/res/icons/167x167.png",
+ "settings" : "unpackage/res/icons/29x29.png",
+ "settings@2x" : "unpackage/res/icons/58x58.png",
+ "spotlight" : "unpackage/res/icons/40x40.png",
+ "spotlight@2x" : "unpackage/res/icons/80x80.png"
+ },
+ "iphone" : {
+ "app@2x" : "unpackage/res/icons/120x120.png",
+ "app@3x" : "unpackage/res/icons/180x180.png",
+ "notification@2x" : "unpackage/res/icons/40x40.png",
+ "notification@3x" : "unpackage/res/icons/60x60.png",
+ "settings@2x" : "unpackage/res/icons/58x58.png",
+ "settings@3x" : "unpackage/res/icons/87x87.png",
+ "spotlight@2x" : "unpackage/res/icons/80x80.png",
+ "spotlight@3x" : "unpackage/res/icons/120x120.png"
+ }
+ }
+ }
+ }
+ },
+ "quickapp" : {},
+ "quickapp-native" : {
+ "icon" : "/static/logo.png",
+ "package" : "com.example.demo",
+ "features" : [
+ {
+ "name" : "system.clipboard"
+ }
+ ]
+ },
+ "quickapp-webview" : {
+ "icon" : "/static/logo.png",
+ "package" : "com.example.demo",
+ "minPlatformVersion" : 1070,
+ "versionName" : "1.0.0",
+ "versionCode" : 100
+ },
+ "mp-weixin" : {
+ "appid" : "wx43051b2afa4ed3d0",
+ "setting" : {
+ "urlCheck" : false,
+ "minified" : true,
+ "postcss" : true
+ },
+ "optimization" : {
+ "subPackages" : true //开启小程序分包优化。
+ },
+ "lazyCodeLoading" : "requiredComponents",
+ "usingComponents" : {},
+ "permission" : {},
+ "requiredPrivateInfos" : [ "chooseAddress" ]
+ },
+ "mp-alipay" : {
+ "usingComponents" : true
+ },
+ "mp-baidu" : {
+ "usingComponents" : true
+ },
+ "mp-toutiao" : {
+ "usingComponents" : true
+ },
+ "mp-jd" : {
+ "usingComponents" : true
+ },
+ "h5" : {
+ "template" : "index.html",
+ "router" : {
+ "mode" : "hash",
+ "base" : "./"
+ },
+ "sdkConfigs" : {
+ "maps" : {}
+ },
+ "async" : {
+ "timeout" : 20000
+ },
+ "title" : "星品购",
+ "optimization" : {
+ "treeShaking" : {
+ "enable" : true
+ }
+ }
+ },
+ "vueVersion" : "3",
+ "_spaceID" : "192b4892-5452-4e1d-9f09-eee1ece40639",
+ "locale" : "zh-Hans",
+ "fallbackLocale" : "zh-Hans"
+}
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..4548e926
--- /dev/null
+++ b/package.json
@@ -0,0 +1,104 @@
+{
+ "id": "shopro",
+ "name": "shopro",
+ "displayName": "星品购",
+ "version": "1.0.1",
+ "description": "Shopro-B2C商城,一套代码,同时发行到iOS、Android、H5、微信小程序多个平台,请使用手机扫码快速体验强大功能",
+ "scripts": {
+ "prettier": "prettier --write \"{pages,sheep}/**/*.{js,json,tsx,css,less,scss,vue,html,md}\""
+ },
+ "repository": "https://github.com/sheepjs/shop.git",
+ "keywords": [
+ "商城",
+ "B2C",
+ "shopro",
+ "商城模板"
+ ],
+ "author": "",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/sheepjs/shop/issues"
+ },
+ "homepage": "https://github.com/dcloudio/hello-uniapp#readme",
+ "dcloudext": {
+ "category": [
+ "前端页面模板",
+ "uni-app前端项目模板"
+ ],
+ "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": "y",
+ "app-nvue": "u"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "u",
+ "百度": "u",
+ "字节跳动": "u",
+ "QQ": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ },
+ "Vue": {
+ "vue2": "u",
+ "vue3": "y"
+ }
+ }
+ }
+ },
+ "dependencies": {
+ "@hyoga/uni-socket.io": "^1.0.1",
+ "dayjs": "^1.11.6",
+ "lodash": "^4.17.21",
+ "pinia": "^2.0.24",
+ "pinia-plugin-persist-uni": "^1.2.0",
+ "qs-canvas": "^1.0.11",
+ "weixin-js-sdk": "^1.6.0"
+ },
+ "devDependencies": {
+ "prettier": "^2.7.1",
+ "vconsole": "^3.15.0"
+ }
+}
diff --git a/pages.json b/pages.json
new file mode 100644
index 00000000..74ed57a9
--- /dev/null
+++ b/pages.json
@@ -0,0 +1,753 @@
+{
+ "easycom": {
+ "autoscan": true,
+ "custom": {
+ "^s-(.*)": "@/sheep/components/s-$1/s-$1.vue",
+ "^su-(.*)": "@/sheep/ui/su-$1/su-$1.vue"
+ }
+ },
+ "pages": [
+ {
+ "path": "pages/index/index",
+ "aliasPath": "/",
+ "style": {
+ "navigationBarTitleText": "首页",
+ "enablePullDownRefresh": true
+ },
+ "meta": {
+ "auth": false,
+ "sync": true,
+ "title": "首页",
+ "group": "商城"
+ }
+ },
+ {
+ "path": "pages/index/user",
+ "style": {
+ "navigationBarTitleText": "个人中心",
+ "enablePullDownRefresh": true
+ },
+ "meta": {
+ "sync": true,
+ "title": "个人中心",
+ "group": "商城"
+ }
+ },
+ {
+ "path": "pages/index/category",
+ "style": {
+ "navigationBarTitleText": "商品分类"
+ },
+ "meta": {
+ "sync": true,
+ "title": "商品分类",
+ "group": "商城"
+ }
+ },
+ {
+ "path": "pages/index/cart",
+ "style": {
+ "navigationBarTitleText": "购物车"
+ },
+ "meta": {
+ "sync": true,
+ "title": "购物车",
+ "group": "商城"
+ }
+ },
+ {
+ "path": "pages/index/login",
+ "style": {
+ "navigationBarTitleText": "登录"
+ }
+ },
+ {
+ "path": "pages/index/search",
+ "style": {
+ "navigationBarTitleText": "搜索"
+ },
+ "meta": {
+ "sync": true,
+ "title": "搜索",
+ "group": "商城"
+ }
+ },
+ {
+ "path": "pages/index/page",
+ "style": {
+ "navigationBarTitleText": ""
+ },
+ "meta": {
+ "auth": false,
+ "sync": true,
+ "title": "自定义页面",
+ "group": "商城"
+ }
+ }
+ ],
+ "subPackages": [
+ {
+ "root": "pages/goods",
+ "pages": [
+ {
+ "path": "index",
+ "style": {
+ "navigationBarTitleText": "商品详情"
+ },
+ "meta": {
+ "sync": true,
+ "title": "普通商品",
+ "group": "商品"
+ }
+ },
+ {
+ "path": "groupon",
+ "style": {
+ "navigationBarTitleText": "拼团商品"
+ },
+ "meta": {
+ "sync": true,
+ "title": "拼团商品",
+ "group": "商品"
+ }
+ },
+
+ {
+ "path": "seckill",
+ "style": {
+ "navigationBarTitleText": "秒杀商品"
+ },
+ "meta": {
+ "sync": true,
+ "title": "秒杀商品",
+ "group": "商品"
+ }
+ },
+
+ {
+ "path": "score",
+ "style": {
+ "navigationBarTitleText": "积分商品"
+ },
+ "meta": {
+ "sync": true,
+ "title": "积分商品",
+ "group": "商品"
+ }
+ },
+ {
+ "path": "list",
+ "style": {
+ "navigationBarTitleText": "商品列表"
+ },
+ "meta": {
+ "sync": true,
+ "title": "商品列表",
+ "group": "商品"
+ }
+ },
+ {
+ "path": "comment/add",
+ "style": {
+ "navigationBarTitleText": "评价商品"
+ },
+ "meta": {
+ "auth": true
+ }
+ },
+ {
+ "path": "comment/list",
+ "style": {
+ "navigationBarTitleText": "商品评价"
+ }
+ }
+ ]
+ },
+ {
+ "root": "pages/order",
+ "pages": [
+ {
+ "path": "detail",
+ "style": {
+ "navigationBarTitleText": "订单详情"
+ },
+ "meta": {
+ "auth": true,
+ "title": "订单详情"
+ }
+ },
+ {
+ "path": "confirm",
+ "style": {
+ "navigationBarTitleText": "确认订单"
+ },
+ "meta": {
+ "auth": true,
+ "title": "确认订单"
+ }
+ },
+ {
+ "path": "list",
+ "style": {
+ "navigationBarTitleText": "我的订单",
+ "enablePullDownRefresh": true
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "用户订单",
+ "group": "订单中心"
+ }
+ },
+ {
+ "path": "invoice",
+ "style": {
+ "navigationBarTitleText": "发票详情"
+ },
+ "meta": {
+ "auth": true,
+ "title": "发票详情"
+ }
+ },
+ {
+ "path": "aftersale/apply",
+ "style": {
+ "navigationBarTitleText": "申请售后"
+ },
+ "meta": {
+ "auth": true,
+ "title": "申请售后"
+ }
+ },
+ {
+ "path": "aftersale/list",
+ "style": {
+ "navigationBarTitleText": "售后列表"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "售后订单",
+ "group": "订单中心"
+ }
+ },
+ {
+ "path": "aftersale/detail",
+ "style": {
+ "navigationBarTitleText": "售后详情"
+ },
+ "meta": {
+ "auth": true,
+ "title": "售后详情"
+ }
+ },
+ {
+ "path": "aftersale/log",
+ "style": {
+ "navigationBarTitleText": "售后进度"
+ },
+ "meta": {
+ "auth": true,
+ "title": "售后进度"
+ }
+ },
+ {
+ "path": "express/log",
+ "style": {
+ "navigationBarTitleText": "物流轨迹"
+ },
+ "meta": {
+ "auth": true,
+ "title": "物流轨迹"
+ }
+ },
+ {
+ "path": "express/list",
+ "style": {
+ "navigationBarTitleText": "订单包裹"
+ },
+ "meta": {
+ "auth": true,
+ "title": "订单包裹"
+ }
+ }
+ ]
+ },
+ {
+ "root": "pages/user",
+ "pages": [
+ {
+ "path": "info",
+ "style": {
+ "navigationBarTitleText": "我的信息"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "用户信息",
+ "group": "用户中心"
+ }
+ },
+ {
+ "path": "goods-collect",
+ "style": {
+ "navigationBarTitleText": "我的收藏"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "商品收藏",
+ "group": "用户中心"
+ }
+ },
+ {
+ "path": "goods-log",
+ "style": {
+ "navigationBarTitleText": "我的足迹"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "浏览记录",
+ "group": "用户中心"
+ }
+ },
+ {
+ "path": "address/list",
+ "style": {
+ "navigationBarTitleText": "收货地址"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "地址管理",
+ "group": "用户中心"
+ }
+ },
+ {
+ "path": "address/edit",
+ "style": {
+ "navigationBarTitleText": "编辑地址"
+ },
+ "meta": {
+ "auth": true,
+ "title": "编辑地址"
+ }
+ },
+ {
+ "path": "invoice/list",
+ "style": {
+ "navigationBarTitleText": "发票管理"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "发票管理",
+ "group": "用户中心"
+ }
+ },
+ {
+ "path": "invoice/edit",
+ "style": {
+ "navigationBarTitleText": "编辑发票"
+ },
+ "meta": {
+ "auth": true,
+ "title": "编辑发票"
+ }
+ },
+ {
+ "path": "wallet/money",
+ "style": {
+ "navigationBarTitleText": "我的余额"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "用户余额",
+ "group": "用户中心"
+ }
+ },
+ {
+ "path": "wallet/commission",
+ "style": {
+ "navigationBarTitleText": "我的佣金"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "用户佣金",
+ "group": "分销中心"
+ }
+ },
+ {
+ "path": "wallet/score",
+ "style": {
+ "navigationBarTitleText": "我的积分"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "用户积分",
+ "group": "用户中心"
+ }
+ }
+ ]
+ },
+ {
+ "root": "pages/commission",
+ "pages": [
+ {
+ "path": "index",
+ "style": {
+ "navigationBarTitleText": "分销"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "分销中心",
+ "group": "分销商城"
+ }
+ },
+ {
+ "path": "apply",
+ "style": {
+ "navigationBarTitleText": "申请分销商"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "申请分销商",
+ "group": "分销商城"
+ }
+ },
+ {
+ "path": "goods",
+ "style": {
+ "navigationBarTitleText": "推广商品"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "推广商品",
+ "group": "分销商城"
+ }
+ },
+ {
+ "path": "order",
+ "style": {
+ "navigationBarTitleText": "分销订单"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "分销订单",
+ "group": "分销商城"
+ }
+ },
+ {
+ "path": "share-log",
+ "style": {
+ "navigationBarTitleText": "分享记录"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "分享记录",
+ "group": "分销商城"
+ }
+ },
+ {
+ "path": "team",
+ "style": {
+ "navigationBarTitleText": "我的团队"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "我的团队",
+ "group": "分销商城"
+ }
+ }
+ ]
+ },
+ {
+ "root": "pages/app",
+ "pages": [
+ {
+ "path": "sign",
+ "style": {
+ "navigationBarTitleText": "签到中心"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "签到中心",
+ "group": "应用"
+ }
+ },
+ {
+ "path": "score-shop",
+ "style": {
+ "navigationBarTitleText": "积分商城"
+ },
+ "meta": {
+ "auth": false,
+ "sync": true,
+ "title": "积分商城",
+ "group": "应用"
+ }
+ }
+ ]
+ },
+ {
+ "root": "pages/public",
+ "pages": [
+ {
+ "path": "setting",
+ "style": {
+ "navigationBarTitleText": "系统设置"
+ },
+ "meta": {
+ "sync": true,
+ "title": "系统设置",
+ "group": "通用"
+ }
+ },
+ {
+ "path": "feedback",
+ "style": {
+ "navigationBarTitleText": "问题反馈"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "问题反馈",
+ "group": "通用"
+ }
+ },
+ {
+ "path": "richtext",
+ "style": {
+ "navigationBarTitleText": "富文本"
+ },
+ "meta": {
+ "sync": true,
+ "title": "富文本",
+ "group": "通用"
+ }
+ },
+ {
+ "path": "faq",
+ "style": {
+ "navigationBarTitleText": "常见问题"
+ },
+ "meta": {
+ "sync": true,
+ "title": "常见问题",
+ "group": "通用"
+ }
+ },
+ {
+ "path": "error",
+ "style": {
+ "navigationBarTitleText": "错误页面"
+ }
+ },
+ {
+ "path": "webview",
+ "style": {
+ "navigationBarTitleText": ""
+ }
+ }
+ ]
+ },
+ {
+ "root": "pages/coupon",
+ "pages": [
+ {
+ "path": "list",
+ "style": {
+ "navigationBarTitleText": "领券中心"
+ },
+ "meta": {
+ "sync": true,
+ "title": "领券中心",
+ "group": "优惠券"
+ }
+ },
+ {
+ "path": "detail",
+ "style": {
+ "navigationBarTitleText": "优惠券"
+ },
+ "meta": {
+ "auth": false,
+ "sync": true,
+ "title": "优惠券详情",
+ "group": "优惠券"
+ }
+ }
+ ]
+ },
+ {
+ "root": "pages/chat",
+ "pages": [
+ {
+ "path": "index",
+ "style": {
+ "navigationBarTitleText": "客服"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "客服",
+ "group": "客服"
+ }
+ }
+ ]
+ },
+ {
+ "root": "pages/pay",
+ "pages": [
+ {
+ "path": "index",
+ "style": {
+ "navigationBarTitleText": "收银台"
+ }
+ },
+ {
+ "path": "result",
+ "style": {
+ "navigationBarTitleText": "支付结果"
+ }
+ },
+ {
+ "path": "recharge",
+ "style": {
+ "navigationBarTitleText": "充值余额"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "充值余额",
+ "group": "支付"
+ }
+ },
+ {
+ "path": "recharge-log",
+ "style": {
+ "navigationBarTitleText": "充值记录"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "充值记录",
+ "group": "支付"
+ }
+ },
+ {
+ "path": "withdraw",
+ "style": {
+ "navigationBarTitleText": "申请提现"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "申请提现",
+ "group": "支付"
+ }
+ },
+ {
+ "path": "withdraw-log",
+ "style": {
+ "navigationBarTitleText": "提现记录"
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "提现记录",
+ "group": "支付"
+ }
+ }
+ ]
+ },
+ {
+ "root": "pages/activity",
+ "pages": [
+ {
+ "path": "groupon/detail",
+ "style": {
+ "navigationBarTitleText": "拼团详情"
+ }
+ },
+ {
+ "path": "groupon/order",
+ "style": {
+ "navigationBarTitleText": "我的拼团",
+ "enablePullDownRefresh": true
+ },
+ "meta": {
+ "auth": true,
+ "sync": true,
+ "title": "拼团订单",
+ "group": "营销活动"
+ }
+ },
+ {
+ "path": "index",
+ "style": {
+ "navigationBarTitleText": "营销商品"
+ },
+ "meta": {
+ "sync": true,
+ "title": "营销商品",
+ "group": "营销活动"
+ }
+ },
+ {
+ "path": "groupon/list",
+ "style": {
+ "navigationBarTitleText": "拼团活动"
+ },
+ "meta": {
+ "sync": true,
+ "title": "拼团活动",
+ "group": "营销活动"
+ }
+ },
+ {
+ "path": "seckill/list",
+ "style": {
+ "navigationBarTitleText": "秒杀活动"
+ },
+ "meta": {
+ "sync": true,
+ "title": "秒杀活动",
+ "group": "营销活动"
+ }
+ }
+ ]
+ }
+ ],
+ "globalStyle": {
+ "navigationBarTextStyle": "black",
+ "navigationBarTitleText": "星品购",
+ "navigationBarBackgroundColor": "#FFFFFF",
+ "backgroundColor": "#FFFFFF",
+ "navigationStyle": "custom"
+ },
+ "tabBar": {
+ "list": [
+ {
+ "pagePath": "pages/index/index"
+ },
+ {
+ "pagePath": "pages/index/cart"
+ },
+ {
+ "pagePath": "pages/index/user"
+ }
+ ]
+ }
+}
diff --git a/pages/activity/groupon/detail.vue b/pages/activity/groupon/detail.vue
new file mode 100644
index 00000000..3d4adbef
--- /dev/null
+++ b/pages/activity/groupon/detail.vue
@@ -0,0 +1,505 @@
+
+
+
+
+
+
+
+
+ {{ state.data.num }}人团
+ 已拼{{ state.data.goods.sales }}件
+
+
+
+
+
+
+
+
+
+ 恭喜您~拼团成功
+
+
+
+
+
+ 抱歉~该团已满员
+
+
+
+
+
+
+ {{ state.data.my ? '拼团超时,已自动退款' : '该团已解散' }}
+
+
+
+
+
+
+
+ 拼团已结束,请关注下次活动
+
+
+
+
+ 还差
+ {{ state.data.num - state.data.current_num }}人
+ 拼团成功
+
+ {{ endTime.h }}
+ :
+
+ {{ endTime.m }}
+
+ :
+
+ {{ endTime.s }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 玩法
+
+
+ {{ state.data.activity?.richtext_title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/activity/groupon/list.vue b/pages/activity/groupon/list.vue
new file mode 100644
index 00000000..145ae90a
--- /dev/null
+++ b/pages/activity/groupon/list.vue
@@ -0,0 +1,253 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/activity/groupon/order.vue b/pages/activity/groupon/order.vue
new file mode 100644
index 00000000..6fb9ddb5
--- /dev/null
+++ b/pages/activity/groupon/order.vue
@@ -0,0 +1,281 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ order.num }}人团
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/activity/index.vue b/pages/activity/index.vue
new file mode 100644
index 00000000..0a33581c
--- /dev/null
+++ b/pages/activity/index.vue
@@ -0,0 +1,191 @@
+
+
+
+
+ {{ state.activityInfo.type_text }}:
+
+
+ {{ item }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/activity/seckill/list.vue b/pages/activity/seckill/list.vue
new file mode 100644
index 00000000..854817ae
--- /dev/null
+++ b/pages/activity/seckill/list.vue
@@ -0,0 +1,252 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/app/score-shop.vue b/pages/app/score-shop.vue
new file mode 100644
index 00000000..6e9ea391
--- /dev/null
+++ b/pages/app/score-shop.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/app/sign.vue b/pages/app/sign.vue
new file mode 100644
index 00000000..a2701cb7
--- /dev/null
+++ b/pages/app/sign.vue
@@ -0,0 +1,540 @@
+
+
+
+
+
+
+
+
+ 签到日历
+
+ 已连续签到
+ {{ state.continue_days }}
+ 天
+
+
+
+
+
+
+ {{ state.cur_year || '--' }} 年 {{ state.cur_month || '--' }} 月
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.day < 10 ? '0' + item.day : item.day }}
+
+
+
+
+
+ {{ item.day < 10 ? '0' + item.day : item.day }}
+
+
+
+ {{ item.day < 10 ? '0' + item.day : item.day }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 签到说明
+
+ 1、每日签到固定 {{ state.data.rules.everyday }} 积分
+
+ ,次日递增奖励 {{ state.data.rules.inc_num }} 积分,直到
+ {{ state.data.rules.until_day }} 天之后不再增加
+
+
+
+ 2、
+
+ 连续签到 {{ i.full }} 天,奖励 {{ i.value }} 积分;
+
+
+
+ {{ state.data.rules.discounts?.length > 0 ? '3' : '2' }}、 用户在
+ {{ state.data.rules.replenish_limit }} 天内,可补签
+ {{ state.data.rules.replenish_days }} 天,每次补签消耗
+ {{ state.data.rules.replenish_num }}积分
+
+
+
+
+
+
+
+
+
+ {{ state.signin.score }}积分
+
+ 已连续打卡{{ state.continue_days }}天
+
+
+
+ 签到成功
+
+
+
+
+
+
+
+
+
+
+ 消耗{{ state.data?.rules.replenish_num }}积分
+
+ 已连续打卡{{ state.continue_days }}天
+
+
+
+ 确认补签
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/chat/components/goods.vue b/pages/chat/components/goods.vue
new file mode 100644
index 00000000..7fbc95c7
--- /dev/null
+++ b/pages/chat/components/goods.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+ {{ goodsData.title }}
+
+
+ {{ goodsData.subtitle }}
+
+
+ ¥{{ isArray(goodsData.price) ? goodsData.price[0] : goodsData.price }}
+
+
+
+
+
+
+
+
diff --git a/pages/chat/components/order.vue b/pages/chat/components/order.vue
new file mode 100644
index 00000000..cf57f63b
--- /dev/null
+++ b/pages/chat/components/order.vue
@@ -0,0 +1,122 @@
+
+
+
+ {{ orderData.order_sn }}
+ {{ orderData.create_time.split(' ')[1] }}
+
+
+
+
+
+
+ {{ item.goods_title }}
+
+ 数量:{{ item.goods_num }}
+
+ ¥{{ item.goods_price }}
+ {{ orderData.status_text }}
+
+
+
+
+
+
+
+
+
+ {{ item.goods_title }}
+
+
+ 共{{ orderData.items.length }}件商品
+ 合计 ¥{{ orderData.pay_fee }}
+
+
+ {{ orderData.status_text }}
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/chat/components/select-popup.vue b/pages/chat/components/select-popup.vue
new file mode 100644
index 00000000..ba0f96aa
--- /dev/null
+++ b/pages/chat/components/select-popup.vue
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
diff --git a/pages/chat/emoji.js b/pages/chat/emoji.js
new file mode 100644
index 00000000..83e6e842
--- /dev/null
+++ b/pages/chat/emoji.js
@@ -0,0 +1,58 @@
+export const emojiList = [
+ { name: '[笑掉牙]', file: 'xiaodiaoya.png' },
+ { name: '[可爱]', file: 'keai.png' },
+ { name: '[冷酷]', file: 'lengku.png' },
+ { name: '[闭嘴]', file: 'bizui.png' },
+ { name: '[生气]', file: 'shengqi.png' },
+ { name: '[惊恐]', file: 'jingkong.png' },
+ { name: '[瞌睡]', file: 'keshui.png' },
+ { name: '[大笑]', file: 'daxiao.png' },
+ { name: '[爱心]', file: 'aixin.png' },
+ { name: '[坏笑]', file: 'huaixiao.png' },
+ { name: '[飞吻]', file: 'feiwen.png' },
+ { name: '[疑问]', file: 'yiwen.png' },
+ { name: '[开心]', file: 'kaixin.png' },
+ { name: '[发呆]', file: 'fadai.png' },
+ { name: '[流泪]', file: 'liulei.png' },
+ { name: '[汗颜]', file: 'hanyan.png' },
+ { name: '[惊悚]', file: 'jingshu.png' },
+ { name: '[困~]', file: 'kun.png' },
+ { name: '[心碎]', file: 'xinsui.png' },
+ { name: '[天使]', file: 'tianshi.png' },
+ { name: '[晕]', file: 'yun.png' },
+ { name: '[啊]', file: 'a.png' },
+ { name: '[愤怒]', file: 'fennu.png' },
+ { name: '[睡着]', file: 'shuizhuo.png' },
+ { name: '[面无表情]', file: 'mianwubiaoqing.png' },
+ { name: '[难过]', file: 'nanguo.png' },
+ { name: '[犯困]', file: 'fankun.png' },
+ { name: '[好吃]', file: 'haochi.png' },
+ { name: '[呕吐]', file: 'outu.png' },
+ { name: '[龇牙]', file: 'ziya.png' },
+ { name: '[懵比]', file: 'mengbi.png' },
+ { name: '[白眼]', file: 'baiyan.png' },
+ { name: '[饿死]', file: 'esi.png' },
+ { name: '[凶]', file: 'xiong.png' },
+ { name: '[感冒]', file: 'ganmao.png' },
+ { name: '[流汗]', file: 'liuhan.png' },
+ { name: '[笑哭]', file: 'xiaoku.png' },
+ { name: '[流口水]', file: 'liukoushui.png' },
+ { name: '[尴尬]', file: 'ganga.png' },
+ { name: '[惊讶]', file: 'jingya.png' },
+ { name: '[大惊]', file: 'dajing.png' },
+ { name: '[不好意思]', file: 'buhaoyisi.png' },
+ { name: '[大闹]', file: 'danao.png' },
+ { name: '[不可思议]', file: 'bukesiyi.png' },
+ { name: '[爱你]', file: 'aini.png' },
+ { name: '[红心]', file: 'hongxin.png' },
+ { name: '[点赞]', file: 'dianzan.png' },
+ { name: '[恶魔]', file: 'emo.png' },
+];
+
+export let emojiPage = {};
+emojiList.forEach((item, index) => {
+ if (!emojiPage[Math.floor(index / 30) + 1]) {
+ emojiPage[Math.floor(index / 30) + 1] = [];
+ }
+ emojiPage[Math.floor(index / 30) + 1].push(item);
+});
diff --git a/pages/chat/index.vue b/pages/chat/index.vue
new file mode 100644
index 00000000..f95a59f4
--- /dev/null
+++ b/pages/chat/index.vue
@@ -0,0 +1,851 @@
+
+
+
+ {{ socketState.isConnect ? customerServiceInfo.title : '网络已断开,请检查网络后刷新重试' }}
+
+
+
+
+
+
+
+
+
+ {{ formatTime(item.date) }}
+
+
+
+ {{ item.content.text }}
+
+
+
+
+ 猜你想问
+
+ * {{ item.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 图片
+
+
+
+ 商品
+
+
+
+ 订单
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/chat/socket.js b/pages/chat/socket.js
new file mode 100644
index 00000000..b68dfbf1
--- /dev/null
+++ b/pages/chat/socket.js
@@ -0,0 +1,821 @@
+import { reactive, ref, unref } from 'vue';
+import sheep from '@/sheep';
+import chat from '@/sheep/api/chat';
+import dayjs from 'dayjs';
+import io from '@hyoga/uni-socket.io';
+
+export function useChatWebSocket(socketConfig) {
+ let SocketIo = null;
+
+ // chat状态数据
+ const state = reactive({
+ chatDotNum: 0, //总状态红点
+ chatList: [], //会话信息
+ customerUserInfo: {}, //用户信息
+ customerServerInfo: {
+ //客服信息
+ title: '连接中...',
+ state: 'connecting',
+ avatar: null,
+ nickname: '',
+ },
+ socketState: {
+ isConnect: true, //是否连接成功
+ isConnecting: false, //重连中,不允许新的socket开启。
+ tip: '',
+ },
+ chatHistoryPagination: {
+ page: 0, //当前页
+ list_rows: 10, //每页条数
+ last_id: 0, //最后条ID
+ lastPage: 0, //总共多少页
+ loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
+ },
+ templateChatList: [], //猜你想问
+
+ chatConfig: {}, // 配置信息
+
+ isSendSucces: -1, // 是否发送成功 -1=发送中|0=发送成功|1发送失败
+ });
+
+ /**
+ * 连接初始化
+ * @param {Object} config - 配置信息
+ * @param {Function} callBack -回调函数,有新消息接入,保持底部
+ */
+ const socketInit = (config, callBack) => {
+ state.chatConfig = config;
+ if (SocketIo && SocketIo.connected) return; // 如果socket已经连接,返回false
+ if (state.socketState.isConnecting) return; // 重连中,返回false
+
+ // 启动初始化
+ SocketIo = io(config.chat_domain, {
+ reconnection: true, // 默认 true 是否断线重连
+ reconnectionAttempts: 5, // 默认无限次 断线尝试次数
+ reconnectionDelay: 1000, // 默认 1000,进行下一次重连的间隔。
+ reconnectionDelayMax: 5000, // 默认 5000, 重新连接等待的最长时间 默认 5000
+ randomizationFactor: 0.5, // 默认 0.5 [0-1],随机重连延迟时间
+ timeout: 20000, // 默认 20s
+ transports: ['websocket', 'polling'], // websocket | polling,
+ ...config,
+ });
+
+ // 监听连接
+ SocketIo.on('connect', async (res) => {
+ socketReset(callBack);
+ // socket连接
+ // 用户登录
+ // 顾客登录
+ console.log('socket:connect');
+ });
+ // 监听消息
+ SocketIo.on('message', (res) => {
+ if (res.error === 0) {
+ const { message, sender } = res.data;
+ state.chatList.push(formatMessage(res.data.message));
+
+ // 告诉父级页面
+ // window.parent.postMessage({
+ // chatDotNum: ++state.chatDotNum
+ // })
+ callBack && callBack();
+ }
+ });
+ // 监听客服接入成功
+ SocketIo.on('customer_service_access', (res) => {
+ if (res.error === 0) {
+ editCustomerServerInfo({
+ title: res.data.customer_service.name,
+ state: 'online',
+ avatar: res.data.customer_service.avatar,
+ });
+ state.chatList.push(formatMessage(res.data.message));
+ // callBack && callBack()
+ }
+ });
+ // 监听排队等待
+ SocketIo.on('waiting_queue', (res) => {
+ if (res.error === 0) {
+ editCustomerServerInfo({
+ title: res.data.title,
+ state: 'waiting',
+ avatar: '',
+ });
+ // callBack && callBack()
+ }
+ });
+ // 监听没有客服在线
+ SocketIo.on('no_customer_service', (res) => {
+ if (res.error === 0) {
+ editCustomerServerInfo({
+ title: '暂无客服在线...',
+ state: 'waiting',
+ avatar: '',
+ });
+ }
+ state.chatList.push(formatMessage(res.data.message));
+ // callBack && callBack()
+ });
+ // 监听客服上线
+ SocketIo.on('customer_service_online', (res) => {
+ if (res.error === 0) {
+ editCustomerServerInfo({
+ title: res.data.customer_service.name,
+ state: 'online',
+ avatar: res.data.customer_service.avatar,
+ });
+ }
+ });
+ // 监听客服下线
+ SocketIo.on('customer_service_offline', (res) => {
+ if (res.error === 0) {
+ editCustomerServerInfo({
+ title: res.data.customer_service.name,
+ state: 'offline',
+ avatar: res.data.customer_service.avatar,
+ });
+ }
+ });
+ // 监听客服忙碌
+ SocketIo.on('customer_service_busy', (res) => {
+ if (res.error === 0) {
+ editCustomerServerInfo({
+ title: res.data.customer_service.name,
+ state: 'busy',
+ avatar: res.data.customer_service.avatar,
+ });
+ }
+ });
+ // 监听客服断开链接
+ SocketIo.on('customer_service_break', (res) => {
+ if (res.error === 0) {
+ editCustomerServerInfo({
+ title: '客服服务结束',
+ state: 'offline',
+ avatar: '',
+ });
+ state.socketState.isConnect = false;
+ state.socketState.tip = '当前服务已结束';
+ }
+ state.chatList.push(formatMessage(res.data.message));
+ // callBack && callBack()
+ });
+ // 监听自定义错误 custom_error
+ SocketIo.on('custom_error', (error) => {
+ editCustomerServerInfo({
+ title: error.msg,
+ state: 'offline',
+ avatar: '',
+ });
+ console.log('custom_error:', error);
+ });
+ // 监听错误 error
+ SocketIo.on('error', (error) => {
+ console.log('error:', error);
+ });
+ // 重连失败 connect_error
+ SocketIo.on('connect_error', (error) => {
+ console.log('connect_error');
+ });
+ // 连接上,但无反应 connect_timeout
+ SocketIo.on('connect_timeout', (error) => {
+ console.log(error, 'connect_timeout');
+ });
+ // 服务进程销毁 disconnect
+ SocketIo.on('disconnect', (error) => {
+ console.log(error, 'disconnect');
+ });
+ // 服务重启重连上reconnect
+ SocketIo.on('reconnect', (error) => {
+ console.log(error, 'reconnect');
+ });
+ // 开始重连reconnect_attempt
+ SocketIo.on('reconnect_attempt', (error) => {
+ state.socketState.isConnect = false;
+ state.socketState.isConnecting = true;
+ editCustomerServerInfo({
+ title: `重连中,第${error}次尝试...`,
+ state: 'waiting',
+ avatar: '',
+ });
+ console.log(error, 'reconnect_attempt');
+ });
+ // 重新连接中reconnecting
+ SocketIo.on('reconnecting', (error) => {
+ console.log(error, 'reconnecting');
+ });
+ // 重新连接错误reconnect_error
+ SocketIo.on('reconnect_error', (error) => {
+ console.log('reconnect_error');
+ });
+ // 重新连接失败reconnect_failed
+ SocketIo.on('reconnect_failed', (error) => {
+ state.socketState.isConnecting = false;
+ editCustomerServerInfo({
+ title: `重连失败,请刷新重试~`,
+ state: 'waiting',
+ avatar: '',
+ });
+ console.log(error, 'reconnect_failed');
+
+ // setTimeout(() => {
+ state.isSendSucces = 1;
+ // }, 500)
+ });
+ };
+
+ // 重置socket
+ const socketReset = (callBack) => {
+ state.chatList = [];
+ state.chatHistoryList = [];
+ state.chatHistoryPagination = {
+ page: 0,
+ per_page: 10,
+ last_id: 0,
+ totalPage: 0,
+ };
+ socketConnection(callBack); // 连接
+ };
+
+ // 退出连接
+ const socketClose = () => {
+ SocketIo.emit('customer_logout', {}, (res) => {
+ console.log('socket:退出', res);
+ });
+ };
+
+ // 测试事件
+ const socketTest = () => {
+ SocketIo.emit('test', {}, (res) => {
+ console.log('test:test', res);
+ });
+ };
+
+ // 发送消息
+ const socketSendMsg = (data, sendMsgCallBack) => {
+ state.isSendSucces = -1;
+ state.chatList.push(data);
+ sendMsgCallBack && sendMsgCallBack();
+ SocketIo.emit(
+ 'message',
+ {
+ message: formatInput(data),
+ ...data.customData,
+ },
+ (res) => {
+ // setTimeout(() => {
+ state.isSendSucces = res.error;
+ // }, 500)
+
+ // console.log(res, 'socket:send');
+ // sendMsgCallBack && sendMsgCallBack()
+ },
+ );
+ };
+
+ // 连接socket,存入sessionId
+ const socketConnection = (callBack) => {
+ SocketIo.emit(
+ 'connection',
+ {
+ auth: 'user',
+ token: uni.getStorageSync('socketUserToken') || '',
+ session_id: uni.getStorageSync('socketSessionId') || '',
+ },
+ (res) => {
+ if (res.error === 0) {
+ socketCustomerLogin(callBack);
+ uni.setStorageSync('socketSessionId', res.data.session_id);
+ // uni.getStorageSync('socketUserToken') && socketLogin(uni.getStorageSync(
+ // 'socketUserToken')) // 如果有用户token,绑定
+ state.customerUserInfo = res.data.chat_user;
+ state.socketState.isConnect = true;
+ } else {
+ editCustomerServerInfo({
+ title: `服务器异常!`,
+ state: 'waiting',
+ avatar: '',
+ });
+ state.socketState.isConnect = false;
+ }
+ },
+ );
+ };
+
+ // 用户id,获取token
+ const getUserToken = async (id) => {
+ const res = await chat.unifiedToken();
+ if (res.error === 0) {
+ uni.setStorageSync('socketUserToken', res.data.token);
+ // SocketIo && SocketIo.connected && socketLogin(res.data.token)
+ }
+ return res;
+ };
+
+ // 用户登录
+ const socketLogin = (token) => {
+ SocketIo.emit(
+ 'login',
+ {
+ token: token,
+ },
+ (res) => {
+ console.log(res, 'socket:login');
+ state.customerUserInfo = res.data.chat_user;
+ },
+ );
+ };
+
+ // 顾客登录
+ const socketCustomerLogin = (callBack) => {
+ SocketIo.emit(
+ 'customer_login',
+ {
+ room_id: state.chatConfig.room_id,
+ },
+ (res) => {
+ state.templateChatList = res.data.questions.length ? res.data.questions : [];
+ state.chatList.push({
+ from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间
+ mode: 'template', // goods,order,image,text,system
+ date: new Date().getTime(), //时间
+ content: {
+ //内容
+ list: state.templateChatList,
+ },
+ });
+ res.error === 0 && socketHistoryList(callBack);
+ },
+ );
+ };
+
+ // 获取历史消息
+ const socketHistoryList = (historyCallBack) => {
+ state.chatHistoryPagination.loadStatus = 'loading';
+ state.chatHistoryPagination.page += 1;
+ SocketIo.emit('messages', state.chatHistoryPagination, (res) => {
+ if (res.error === 0) {
+ state.chatHistoryPagination.total = res.data.messages.total;
+ state.chatHistoryPagination.lastPage = res.data.messages.last_page;
+ state.chatHistoryPagination.page = res.data.messages.current_page;
+ res.data.messages.data.forEach((item) => {
+ item.message_type && state.chatList.unshift(formatMessage(item));
+ });
+ state.chatHistoryPagination.loadStatus =
+ state.chatHistoryPagination.page < state.chatHistoryPagination.lastPage
+ ? 'loadmore'
+ : 'nomore';
+ if (state.chatHistoryPagination.last_id == 0) {
+ state.chatHistoryPagination.last_id = res.data.messages.data.length
+ ? res.data.messages.data[0].id
+ : 0;
+ }
+ state.chatHistoryPagination.page === 1 && historyCallBack && historyCallBack();
+ }
+
+ // 历史记录之后,猜你想问
+ // state.chatList.push({
+ // from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间
+ // mode: 'template', // goods,order,image,text,system
+ // date: new Date().getTime(), //时间
+ // content: { //内容
+ // list: state.templateChatList
+ // }
+ // })
+ });
+ };
+
+ // 修改客服信息
+ const editCustomerServerInfo = (data) => {
+ state.customerServerInfo = {
+ ...state.customerServerInfo,
+ ...data,
+ };
+ };
+
+ /**
+ * ================
+ * 工具函数 ↓
+ * ===============
+ */
+
+ /**
+ * 是否显示时间
+ * @param {*} item - 数据
+ * @param {*} index - 索引
+ */
+ const showTime = (item, index) => {
+ if (unref(state.chatList)[index + 1]) {
+ let dateString = dayjs(unref(state.chatList)[index + 1].date).fromNow();
+ if (dateString === dayjs(unref(item).date).fromNow()) {
+ return false;
+ } else {
+ dateString = dayjs(unref(item).date).fromNow();
+ return true;
+ }
+ }
+ return false;
+ };
+
+ /**
+ * 格式化时间
+ * @param {*} time - 时间戳
+ */
+ const formatTime = (time) => {
+ let diffTime = new Date().getTime() - time;
+ if (diffTime > 28 * 24 * 60 * 1000) {
+ return dayjs(time).format('MM/DD HH:mm');
+ }
+ if (diffTime > 360 * 28 * 24 * 60 * 1000) {
+ return dayjs(time).format('YYYY/MM/DD HH:mm');
+ }
+ return dayjs(time).fromNow();
+ };
+
+ /**
+ * 获取焦点
+ * @param {*} virtualNode - 节点信息 ref
+ */
+ const getFocus = (virtualNode) => {
+ if (window.getSelection) {
+ let chatInput = unref(virtualNode);
+ chatInput.focus();
+ let range = window.getSelection();
+ range.selectAllChildren(chatInput);
+ range.collapseToEnd();
+ } else if (document.selection) {
+ let range = document.selection.createRange();
+ range.moveToElementText(chatInput);
+ range.collapse(false);
+ range.select();
+ }
+ };
+
+ /**
+ * 文件上传
+ * @param {Blob} file -文件数据流
+ * @return {path,fullPath}
+ */
+
+ const upload = (name, file) => {
+ return new Promise((resolve, reject) => {
+ let data = new FormData();
+ data.append('file', file, name);
+ data.append('group', 'chat');
+ ajax({
+ url: '/upload',
+ method: 'post',
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ data,
+ success: function (res) {
+ resolve(res);
+ },
+ error: function (err) {
+ reject(err);
+ },
+ });
+ });
+ };
+
+ /**
+ * 粘贴到输入框
+ * @param {*} e - 粘贴内容
+ * @param {*} uploadHttp - 上传图片地址
+ */
+ const onPaste = async (e) => {
+ let paste = e.clipboardData || window.clipboardData;
+ let filesArr = Array.from(paste.files);
+ filesArr.forEach(async (child) => {
+ if (child && child.type.includes('image')) {
+ e.preventDefault(); //阻止默认
+ let file = child;
+ const img = await readImg(file);
+ const blob = await compressImg(img, file.type);
+ const { data } = await upload(file.name, blob);
+ let image = `
`;
+ document.execCommand('insertHTML', false, image);
+ } else {
+ document.execCommand('insertHTML', false, paste.getData('text'));
+ }
+ });
+ };
+
+ /**
+ * 拖拽到输入框
+ * @param {*} e - 粘贴内容
+ * @param {*} uploadHttp - 上传图片地址
+ */
+ const onDrop = async (e) => {
+ e.preventDefault(); //阻止默认
+ let filesArr = Array.from(e.dataTransfer.files);
+ filesArr.forEach(async (child) => {
+ if (child && child.type.includes('image')) {
+ let file = child;
+ const img = await readImg(file);
+ const blob = await compressImg(img, file.type);
+ const { data } = await upload(file.name, blob);
+ let image = `
`;
+ document.execCommand('insertHTML', false, image);
+ } else {
+ ElMessage({
+ message: '禁止拖拽非图片资源',
+ type: 'warning',
+ });
+ }
+ });
+ };
+
+ /**
+ * 解析富文本输入框内容
+ * @param {*} virtualNode -节点信息
+ * @param {Function} formatInputCallBack - cb 回调
+ */
+ const formatChatInput = (virtualNode, formatInputCallBack) => {
+ let res = '';
+ let elemArr = Array.from(virtualNode.childNodes);
+ elemArr.forEach((child, index) => {
+ if (child.nodeName === '#text') {
+ //如果为文本节点
+ res += child.nodeValue;
+ if (
+ //文本节点的后面是图片,并且不是emoji,分开发送。输入框中的图片和文本表情分开。
+ elemArr[index + 1] &&
+ elemArr[index + 1].nodeName === 'IMG' &&
+ elemArr[index + 1] &&
+ elemArr[index + 1].name !== 'emoji'
+ ) {
+ const data = {
+ from: 'customer',
+ mode: 'text',
+ date: new Date().getTime(),
+ content: {
+ text: filterXSS(res),
+ },
+ };
+ formatInputCallBack && formatInputCallBack(data);
+ res = '';
+ }
+ } else if (child.nodeName === 'BR') {
+ res += '
';
+ } else if (child.nodeName === 'IMG') {
+ // 有emjio 和 一般图片
+ // 图片解析后直接发送,不跟文字表情一组
+ if (child.name !== 'emoji') {
+ let srcReg = /src=[\'\']?([^\'\']*)[\'\']?/i;
+ let src = child.outerHTML.match(srcReg);
+ const data = {
+ from: 'customer',
+ mode: 'image',
+ date: new Date().getTime(),
+ content: {
+ url: src[1],
+ path: src[1].replace(/http:\/\/[^\/]*/, ''),
+ },
+ };
+ formatInputCallBack && formatInputCallBack(data);
+ } else {
+ // 非表情图片跟文字一起发送
+ res += child.outerHTML;
+ }
+ } else if (child.nodeName === 'DIV') {
+ res += `${child.outerHTML}
`;
+ }
+ });
+ if (res) {
+ const data = {
+ from: 'customer',
+ mode: 'text',
+ date: new Date().getTime(),
+ content: {
+ text: filterXSS(res),
+ },
+ };
+ formatInputCallBack && formatInputCallBack(data);
+ }
+ unref(virtualNode).innerHTML = '';
+ };
+
+ /**
+ * 状态回调
+ * @param {*} res -接口返回数据
+ */
+ const callBackNotice = (res) => {
+ ElNotification({
+ title: 'socket',
+ message: res.msg,
+ showClose: true,
+ type: res.error === 0 ? 'success' : 'warning',
+ duration: 1200,
+ });
+ };
+
+ /**
+ * 格式化发送信息
+ * @param {Object} message
+ * @returns obj - 消息对象
+ */
+ const formatInput = (message) => {
+ let obj = {};
+ switch (message.mode) {
+ case 'text':
+ obj = {
+ message_type: 'text',
+ message: message.content.text,
+ };
+ break;
+ case 'image':
+ obj = {
+ message_type: 'image',
+ message: message.content.path,
+ };
+ break;
+ case 'goods':
+ obj = {
+ message_type: 'goods',
+ message: message.content.item,
+ };
+ break;
+ case 'order':
+ obj = {
+ message_type: 'order',
+ message: message.content.item,
+ };
+ break;
+ default:
+ break;
+ }
+ return obj;
+ };
+ /**
+ * 格式化接收信息
+ * @param {*} message
+ * @returns obj - 消息对象
+ */
+ const formatMessage = (message) => {
+ let obj = {};
+ switch (message.message_type) {
+ case 'system':
+ obj = {
+ from: 'system', // 用户customer左 | 顾客customer_service右 | 系统system中间
+ mode: 'system', // goods,order,image,text,system
+ date: message.create_time * 1000, //时间
+ content: {
+ //内容
+ text: message.message,
+ },
+ };
+ break;
+ case 'text':
+ obj = {
+ from: message.sender_identify,
+ mode: message.message_type,
+ date: message.create_time * 1000, //时间
+ sender: message.sender,
+ content: {
+ text: message.message,
+ messageId: message.id,
+ },
+ };
+ break;
+ case 'image':
+ obj = {
+ from: message.sender_identify,
+ mode: message.message_type,
+ date: message.create_time * 1000, //时间
+ sender: message.sender,
+ content: {
+ url: sheep.$url.cdn(message.message),
+ messageId: message.id,
+ },
+ };
+ break;
+ case 'goods':
+ obj = {
+ from: message.sender_identify,
+ mode: message.message_type,
+ date: message.create_time * 1000, //时间
+ sender: message.sender,
+ content: {
+ item: message.message,
+ messageId: message.id,
+ },
+ };
+ break;
+ case 'order':
+ obj = {
+ from: message.sender_identify,
+ mode: message.message_type,
+ date: message.create_time * 1000, //时间
+ sender: message.sender,
+ content: {
+ item: message.message,
+ messageId: message.id,
+ },
+ };
+ break;
+ default:
+ break;
+ }
+ return obj;
+ };
+
+ /**
+ * file 转换为 img
+ * @param {*} file - file 文件
+ * @returns img - img标签
+ */
+ const readImg = (file) => {
+ return new Promise((resolve, reject) => {
+ const img = new Image();
+ const reader = new FileReader();
+ reader.onload = function (e) {
+ img.src = e.target.result;
+ };
+ reader.onerror = function (e) {
+ reject(e);
+ };
+ reader.readAsDataURL(file);
+ img.onload = function () {
+ resolve(img);
+ };
+ img.onerror = function (e) {
+ reject(e);
+ };
+ });
+ };
+
+ /**
+ * 压缩图片
+ *@param img -被压缩的img对象
+ * @param type -压缩后转换的文件类型
+ * @param mx -触发压缩的图片最大宽度限制
+ * @param mh -触发压缩的图片最大高度限制
+ * @returns blob - 文件流
+ */
+ const compressImg = (img, type = 'image/jpeg', mx = 1000, mh = 1000, quality = 1) => {
+ return new Promise((resolve, reject) => {
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ const { width: originWidth, height: originHeight } = img;
+ // 最大尺寸限制
+ const maxWidth = mx;
+ const maxHeight = mh;
+ // 目标尺寸
+ let targetWidth = originWidth;
+ let targetHeight = originHeight;
+ if (originWidth > maxWidth || originHeight > maxHeight) {
+ if (originWidth / originHeight > 1) {
+ // 宽图片
+ targetWidth = maxWidth;
+ targetHeight = Math.round(maxWidth * (originHeight / originWidth));
+ } else {
+ // 高图片
+ targetHeight = maxHeight;
+ targetWidth = Math.round(maxHeight * (originWidth / originHeight));
+ }
+ }
+ canvas.width = targetWidth;
+ canvas.height = targetHeight;
+ context.clearRect(0, 0, targetWidth, targetHeight);
+ // 图片绘制
+ context.drawImage(img, 0, 0, targetWidth, targetHeight);
+ canvas.toBlob(
+ function (blob) {
+ resolve(blob);
+ },
+ type,
+ quality,
+ );
+ });
+ };
+
+ return {
+ compressImg,
+ readImg,
+ formatMessage,
+ formatInput,
+ callBackNotice,
+
+ socketInit,
+ socketSendMsg,
+ socketClose,
+ socketHistoryList,
+
+ getFocus,
+ formatChatInput,
+ onDrop,
+ onPaste,
+ upload,
+
+ getUserToken,
+
+ state,
+
+ socketTest,
+
+ showTime,
+ formatTime,
+ };
+}
diff --git a/pages/commission/apply.vue b/pages/commission/apply.vue
new file mode 100644
index 00000000..174de72a
--- /dev/null
+++ b/pages/commission/apply.vue
@@ -0,0 +1,285 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/commission/components/account-info.vue b/pages/commission/components/account-info.vue
new file mode 100644
index 00000000..d0463e61
--- /dev/null
+++ b/pages/commission/components/account-info.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+ 总收益(元)
+
+ {{ state.showMoney ? agentInfo.total_income || '0.00' : '***' }}
+
+
+
+ 我的佣金(元)
+
+ {{ state.showMoney ? userInfo.commission || '0.00' : '***' }}
+
+
+
+ 我的消费(元)
+
+ {{ state.showMoney ? userInfo.total_consume || '0.00' : '***' }}
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/commission/components/commission-auth.vue b/pages/commission/components/commission-auth.vue
new file mode 100644
index 00000000..964f6618
--- /dev/null
+++ b/pages/commission/components/commission-auth.vue
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+
+ {{ state.event.title }}
+ {{ state.event.subtitle }}
+
+
+
+
+
+
+
+
+
diff --git a/pages/commission/components/commission-condition.vue b/pages/commission/components/commission-condition.vue
new file mode 100644
index 00000000..92194201
--- /dev/null
+++ b/pages/commission/components/commission-condition.vue
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+ {{ item.subtitle }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ errorData.value }}元
+
+ {{ userInfo.total_consume }}元
+
+
+
+ * 购买指定商品即可成为分销商
+ * 满足累计消费即可成为分销商
+
+
+
+
+
+
+
diff --git a/pages/commission/components/commission-info.vue b/pages/commission/components/commission-info.vue
new file mode 100644
index 00000000..fca50ef1
--- /dev/null
+++ b/pages/commission/components/commission-info.vue
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+
+
+ {{ userInfo.nickname }}
+
+
+
+
+ {{ agentInfo.level_info?.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/commission/components/commission-log.vue b/pages/commission/components/commission-log.vue
new file mode 100644
index 00000000..125712e1
--- /dev/null
+++ b/pages/commission/components/commission-log.vue
@@ -0,0 +1,181 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.remark }}
+
+
+ {{ dayjs(item.create_time).fromNow() }}
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/commission/components/commission-menu.vue b/pages/commission/components/commission-menu.vue
new file mode 100644
index 00000000..5a16c451
--- /dev/null
+++ b/pages/commission/components/commission-menu.vue
@@ -0,0 +1,153 @@
+
+
+
+
+
+
+
+
diff --git a/pages/commission/goods.vue b/pages/commission/goods.vue
new file mode 100644
index 00000000..53085b5d
--- /dev/null
+++ b/pages/commission/goods.vue
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+ 预计佣金:¥{{ item.commission }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/commission/index.vue b/pages/commission/index.vue
new file mode 100644
index 00000000..3a4c4e24
--- /dev/null
+++ b/pages/commission/index.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/commission/order.vue b/pages/commission/order.vue
new file mode 100644
index 00000000..40f02a80
--- /dev/null
+++ b/pages/commission/order.vue
@@ -0,0 +1,416 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 佣金:
+ {{ item.rewards[0]?.commission }}
+
+
+ {{ item.commission_order_status_text }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/commission/share-log.vue b/pages/commission/share-log.vue
new file mode 100644
index 00000000..db883c98
--- /dev/null
+++ b/pages/commission/share-log.vue
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.user?.nickname }}
+
+
+
+
+
+
+
+ {{ item.ext?.memo }}
+
+
+
+
+ {{ dayjs(item.create_time).fromNow() }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/commission/team.vue b/pages/commission/team.vue
new file mode 100644
index 00000000..c7748c13
--- /dev/null
+++ b/pages/commission/team.vue
@@ -0,0 +1,261 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.agent.level_info.name }}
+
+
+ {{ item.create_time }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/coupon/detail.vue b/pages/coupon/detail.vue
new file mode 100644
index 00000000..5218df46
--- /dev/null
+++ b/pages/coupon/detail.vue
@@ -0,0 +1,371 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ state.list.name }}
+ {{ state.list.amount_text }}
+
+
+ 领取时间:{{ state.list.get_start_time }}至{{ state.list.get_end_time }}
+
+
+ 有效期:{{ state.list.use_start_time }}至{{ state.list.use_end_time }}
+
+
+
+
+
+ 优惠券类型
+ {{ state.list.type_text }}
+
+
+
+
+ {{ state.list.description }}
+
+
+
+
+
+
+
+
+ {{ state.list.use_scope_text }}
+
+
+
+ {{ state.list.use_scope_text }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/coupon/list.vue b/pages/coupon/list.vue
new file mode 100644
index 00000000..2922699c
--- /dev/null
+++ b/pages/coupon/list.vue
@@ -0,0 +1,244 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/comment/add.vue b/pages/goods/comment/add.vue
new file mode 100644
index 00000000..0392516e
--- /dev/null
+++ b/pages/goods/comment/add.vue
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ rateMap[state.commentList[index].level] }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/comment/list.vue b/pages/goods/comment/list.vue
new file mode 100644
index 00000000..8426ac43
--- /dev/null
+++ b/pages/goods/comment/list.vue
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+ {{ item.user_nickname }}
+
+
+ {{ item.create_time?.substring(0, 11) }}
+
+ {{ item.content }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/comment-item.vue b/pages/goods/components/detail/comment-item.vue
new file mode 100644
index 00000000..b724e0be
--- /dev/null
+++ b/pages/goods/components/detail/comment-item.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+ {{ item.user_nickname }}
+
+
+
+
+
+
+ {{ item.content }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-activity-tip.vue b/pages/goods/components/detail/detail-activity-tip.vue
new file mode 100644
index 00000000..5f0ca1ac
--- /dev/null
+++ b/pages/goods/components/detail/detail-activity-tip.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+ 该商品正在参与{{ activity.type_text }}活动
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-cell-params.vue b/pages/goods/components/detail/detail-cell-params.vue
new file mode 100644
index 00000000..d7f31c4a
--- /dev/null
+++ b/pages/goods/components/detail/detail-cell-params.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+ {{ item.title }}
+ {{ item.content }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-cell-service.vue b/pages/goods/components/detail/detail-cell-service.vue
new file mode 100644
index 00000000..7a018294
--- /dev/null
+++ b/pages/goods/components/detail/detail-cell-service.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.name }}
+ {{ item.description }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-cell-sku.vue b/pages/goods/components/detail/detail-cell-sku.vue
new file mode 100644
index 00000000..3ba9739a
--- /dev/null
+++ b/pages/goods/components/detail/detail-cell-sku.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-cell.vue b/pages/goods/components/detail/detail-cell.vue
new file mode 100644
index 00000000..b29f3669
--- /dev/null
+++ b/pages/goods/components/detail/detail-cell.vue
@@ -0,0 +1,60 @@
+
+
+ {{ label }}
+ {{ value }}
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-comment-card.vue b/pages/goods/components/detail/detail-comment-card.vue
new file mode 100644
index 00000000..10b4fa37
--- /dev/null
+++ b/pages/goods/components/detail/detail-comment-card.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-content-card.vue b/pages/goods/components/detail/detail-content-card.vue
new file mode 100644
index 00000000..f7e048dc
--- /dev/null
+++ b/pages/goods/components/detail/detail-content-card.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-navbar.vue b/pages/goods/components/detail/detail-navbar.vue
new file mode 100644
index 00000000..d90b5e10
--- /dev/null
+++ b/pages/goods/components/detail/detail-navbar.vue
@@ -0,0 +1,255 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-progress.vue b/pages/goods/components/detail/detail-progress.vue
new file mode 100644
index 00000000..d4a6dd0d
--- /dev/null
+++ b/pages/goods/components/detail/detail-progress.vue
@@ -0,0 +1,39 @@
+
+
+ 已抢{{ percent }}%
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-skeleton.vue b/pages/goods/components/detail/detail-skeleton.vue
new file mode 100644
index 00000000..5eebf50c
--- /dev/null
+++ b/pages/goods/components/detail/detail-skeleton.vue
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/detail/detail-tabbar.vue b/pages/goods/components/detail/detail-tabbar.vue
new file mode 100644
index 00000000..aec21ede
--- /dev/null
+++ b/pages/goods/components/detail/detail-tabbar.vue
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+ 已收藏
+
+
+
+ 收藏
+
+
+
+
+ 客服
+
+
+
+ 分享
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/groupon/groupon-card-list.vue b/pages/goods/components/groupon/groupon-card-list.vue
new file mode 100644
index 00000000..0688a86b
--- /dev/null
+++ b/pages/goods/components/groupon/groupon-card-list.vue
@@ -0,0 +1,137 @@
+
+
+
+ 已有{{ modelValue.sales }}人参与活动
+
+
+
+
+
+ {{ item.leader.nickname }}
+
+
+
+
+ 还差
+ {{ item.num - item.current_num }}人
+ 成团
+
+ {{ endTime(item.expire_time) }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/list/list-goods-card.vue b/pages/goods/components/list/list-goods-card.vue
new file mode 100644
index 00000000..8858125e
--- /dev/null
+++ b/pages/goods/components/list/list-goods-card.vue
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+ {{ title }}
+ {{ subTitle }}
+
+ {{ price }}
+ {{ originPrice }}
+ 已售{{ sales }}件
+
+
+
+
+
+
+
+
diff --git a/pages/goods/components/list/list-navbar.vue b/pages/goods/components/list/list-navbar.vue
new file mode 100644
index 00000000..15597d5d
--- /dev/null
+++ b/pages/goods/components/list/list-navbar.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/groupon.vue b/pages/goods/groupon.vue
new file mode 100644
index 00000000..f0d1a001
--- /dev/null
+++ b/pages/goods/groupon.vue
@@ -0,0 +1,572 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ goodsPrice }}
+
+
+
+
+
+
+
+ 拼团价
+
+
+
+
+ 单买价:
+
+ {{ state.goodsInfo.original_goods_price[0] || state.goodsInfo.original_price }}
+
+
+
+
+
+
+ 距结束仅剩
+
+ {{ endTime.h }}
+ :
+ {{ endTime.m }}
+ :
+ {{ endTime.s }}
+
+
+ 活动已结束
+
+
+ {{ state.goodsInfo.title }}
+ {{ state.goodsInfo.subtitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/index.vue b/pages/goods/index.vue
new file mode 100644
index 00000000..dc95e1f9
--- /dev/null
+++ b/pages/goods/index.vue
@@ -0,0 +1,399 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ state.selectedSkuPrice.price || formatPrice(state.goodsInfo.price) }}
+
+
+ {{ state.selectedSkuPrice.original_price || state.goodsInfo.original_price }}
+
+
+
+ {{ formatSales(state.goodsInfo.sales_show_type, state.goodsInfo.sales) }}
+
+
+
+
+
+
+ {{ promos.title }}
+
+
+
+
+
+ 领券
+
+
+
+ {{ state.goodsInfo.title }}
+ {{ state.goodsInfo.subtitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/list.vue b/pages/goods/list.vue
new file mode 100644
index 00000000..e209fecd
--- /dev/null
+++ b/pages/goods/list.vue
@@ -0,0 +1,376 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/score.vue b/pages/goods/score.vue
new file mode 100644
index 00000000..0a1fdb39
--- /dev/null
+++ b/pages/goods/score.vue
@@ -0,0 +1,354 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ¥{{ goodsPrice.price }}
+ +
+
+
+ {{ goodsPrice.score }}
+
+
+
+ {{ formatExchange(state.goodsInfo.sales_show_type, state.goodsInfo.sales) }}
+
+
+
+ 原价:¥{{ state.selectedSkuPrice.original_price || state.goodsInfo.original_price }}
+
+ {{ state.goodsInfo.title }}
+ {{ state.goodsInfo.subtitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/goods/seckill.vue b/pages/goods/seckill.vue
new file mode 100644
index 00000000..02a6c9d0
--- /dev/null
+++ b/pages/goods/seckill.vue
@@ -0,0 +1,528 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ state.selectedSkuPrice.price || formatPrice(state.goodsInfo.price) }}
+
+
+
+
+
+ 秒杀价
+
+
+
+ 距结束仅剩
+
+ {{ endTime.h }}
+ :
+ {{ endTime.m }}
+ :
+ {{ endTime.s }}
+
+
+ 活动已结束
+
+
+
+ 原价
+
+ {{ state.selectedSkuPrice.original_price || state.goodsInfo.original_price }}
+
+
+
+
+
+ {{ state.goodsInfo.title }}
+ {{ state.goodsInfo.subtitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/cart.vue b/pages/index/cart.vue
new file mode 100644
index 00000000..545c6f2f
--- /dev/null
+++ b/pages/index/cart.vue
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/category.vue b/pages/index/category.vue
new file mode 100644
index 00000000..9024ef04
--- /dev/null
+++ b/pages/index/category.vue
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/components/first-one.vue b/pages/index/components/first-one.vue
new file mode 100644
index 00000000..792fcd92
--- /dev/null
+++ b/pages/index/components/first-one.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/components/first-two.vue b/pages/index/components/first-two.vue
new file mode 100644
index 00000000..f1479974
--- /dev/null
+++ b/pages/index/components/first-two.vue
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+ {{ item.title }}
+ ¥{{ item.price[0] }}
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/components/second-one.vue b/pages/index/components/second-one.vue
new file mode 100644
index 00000000..05528374
--- /dev/null
+++ b/pages/index/components/second-one.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
+ {{ props.data.children[activeMenu].name }}
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/components/third-one.vue b/pages/index/components/third-one.vue
new file mode 100644
index 00000000..e4306816
--- /dev/null
+++ b/pages/index/components/third-one.vue
@@ -0,0 +1,92 @@
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+ {{ i.name }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/index.vue b/pages/index/index.vue
new file mode 100644
index 00000000..236a015f
--- /dev/null
+++ b/pages/index/index.vue
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/login.vue b/pages/index/login.vue
new file mode 100644
index 00000000..d2f8ff50
--- /dev/null
+++ b/pages/index/login.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
diff --git a/pages/index/page.vue b/pages/index/page.vue
new file mode 100644
index 00000000..657d1af0
--- /dev/null
+++ b/pages/index/page.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/search.vue b/pages/index/search.vue
new file mode 100644
index 00000000..75d26c18
--- /dev/null
+++ b/pages/index/search.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+ 搜索历史
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/index/user.vue b/pages/index/user.vue
new file mode 100644
index 00000000..a1425311
--- /dev/null
+++ b/pages/index/user.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/order/aftersale/apply.vue b/pages/order/aftersale/apply.vue
new file mode 100644
index 00000000..3248172a
--- /dev/null
+++ b/pages/order/aftersale/apply.vue
@@ -0,0 +1,353 @@
+
+
+
+
+
+
+
+
+
+
+
+ 售后类型
+
+
+
+ {{ item.text }}
+
+
+
+
+
+ 申请原因
+
+ {{ formData.reason }}
+ 请选择申请原因~
+
+
+
+
+
+ 联系方式
+
+
+
+
+
+
+
+ 相关描述
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 申请原因
+
+
+ {{
+ item.title
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/order/aftersale/detail.vue b/pages/order/aftersale/detail.vue
new file mode 100644
index 00000000..23ce633e
--- /dev/null
+++ b/pages/order/aftersale/detail.vue
@@ -0,0 +1,350 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
+ {{ state.info.aftersale_status_desc }}
+ {{ state.info.update_time }}
+
+
+
+
+
+
+ 退款总额
+ ¥{{ state.info.refund_fee }}
+
+
+
+
+
+
+
+
+
+ 服务单号:
+ {{ state.info.aftersale_sn }}
+
+
+
+ 申请时间:
+ {{ state.info.create_time }}
+
+
+ 售后类型:
+ {{ state.info.type_text }}
+
+
+ 申请原因:
+ {{ state.info.reason }}
+
+
+ 相关描述:
+ {{ state.info.content }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/order/aftersale/list.vue b/pages/order/aftersale/list.vue
new file mode 100644
index 00000000..80e3c792
--- /dev/null
+++ b/pages/order/aftersale/list.vue
@@ -0,0 +1,236 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 服务单号:{{ order.aftersale_sn }}
+ {{ order.aftersale_status_text }}
+
+
+
+
+ {{ order.type_text }}
+ {{ order.aftersale_status_desc }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/order/aftersale/log.vue b/pages/order/aftersale/log.vue
new file mode 100644
index 00000000..4dbb4af8
--- /dev/null
+++ b/pages/order/aftersale/log.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+ {{ item.log_type_text }}
+
+
+
+
+
+
+
+
+
+ {{ item.create_time }}
+
+
+
+
+
+
+
+
+
diff --git a/pages/order/confirm.vue b/pages/order/confirm.vue
new file mode 100644
index 00000000..0aed3dce
--- /dev/null
+++ b/pages/order/confirm.vue
@@ -0,0 +1,404 @@
+
+
+
+
+
+
+
+
+
+
+
+ 配送方式
+
+ 物流快递
+
+
+
+ 订单备注
+
+
+
+
+
+
+
+
+
+ 商品金额
+
+ ¥{{ state.orderInfo.goods_amount }}
+
+
+
+ 扣除积分
+
+
+ {{ state.orderInfo.score_amount }}
+
+
+
+ 运费
+
+ +¥{{ state.orderInfo.dispatch_amount }}
+
+
+
+
+ 优惠券
+
+ -¥{{ state.orderInfo.coupon_discount_fee }}
+ {{ state.couponInfo.can_use?.length }}张可用
+
+
+
+
+
+
+ 活动优惠
+
+ -¥{{ state.orderInfo.promo_discount_fee }}
+
+
+
+
+
+
+
+
+
+ 发票申请
+
+ {{ state.invoiceInfo.name || '无需开具发票' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/order/detail.vue b/pages/order/detail.vue
new file mode 100644
index 00000000..f462baf8
--- /dev/null
+++ b/pages/order/detail.vue
@@ -0,0 +1,541 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ state.orderInfo.status_text }}
+
+ {{ state.orderInfo.status_desc }}
+
+
+
+
+
+
+ {{ state.orderInfo.address?.consignee }}
+
+ {{ state.orderInfo.address?.mobile }}
+
+ {{ addressText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 订单编号:
+ {{ state.orderInfo.order_sn }}
+
+
+
+
+ 下单时间:
+ {{ state.orderInfo.create_time }}
+
+
+ 支付时间:
+ {{ state.orderInfo.paid_time || '-' }}
+
+
+ 支付方式:
+ {{ state.orderInfo.pay_types_text?.join(',') || '-' }}
+
+
+
+
+
+
+ 商品总额
+
+ ¥{{ state.orderInfo.goods_amount }}
+ +
+
+
+ {{ state.orderInfo.score_amount }}
+
+
+
+
+ 运费
+ ¥{{ state.orderInfo.dispatch_amount }}
+
+
+ 优惠金额
+ ¥{{ state.orderInfo.promo_discount_fee }}
+
+
+ 已付款
+ ¥{{ state.orderInfo.pay_fee }}
+ +
+
+
+ {{ state.orderInfo.score_amount }}
+
+
+
+ 已退款
+ ¥{{ refundFee.toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/order/express/list.vue b/pages/order/express/list.vue
new file mode 100644
index 00000000..5e285a27
--- /dev/null
+++ b/pages/order/express/list.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+ {{ item.logs[0]?.content }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/order/express/log.vue b/pages/order/express/log.vue
new file mode 100644
index 00000000..204edfdd
--- /dev/null
+++ b/pages/order/express/log.vue
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 物流状态:
+ {{ state.info.status_text }}
+
+ 快递单号:{{ state.info.express_no }}
+ 快递公司:{{ state.info.express_name }}
+
+
+
+
+
+
+
+
+
+
+ {{ item.status_text }}
+ {{ item.content }}
+ {{ item.change_date }}
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/order/invoice.vue b/pages/order/invoice.vue
new file mode 100644
index 00000000..551f7d25
--- /dev/null
+++ b/pages/order/invoice.vue
@@ -0,0 +1,328 @@
+
+
+
+
+
+
+
+ {{ state.data.status_text }}
+
+
+ 预计可开发票金额:
+ ¥{{ state.data.amount }}
+
+
+
+ 增值税电子普通发票
+
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+ 发票类型
+ {{ state.data.type_text }}
+
+
+ 发票抬头
+ {{ state.data.name }}
+
+
+ 发票税号
+ {{ state.data.tax_no }}
+
+
+ 开票金额
+ ¥{{ state.data.amount }}
+
+
+ 开票时间
+ {{ state.data.finish_time }}
+
+
+ 申请时间
+ {{ state.data.create_time }}
+
+
+
+
+
+
+
+
+ 共{{ state.numImage }}张
+ 点击预览发票
+
+
+
+
+
+
+
+
+
+ 订单状态
+ {{ state.data.order?.status_text }}
+
+
+ 订单编号
+ {{ state.data.order?.order_sn }}
+
+
+ 下单时间
+ {{ state.data.order?.create_time }}
+
+
+
+
+
+
+
+
+
diff --git a/pages/order/list.vue b/pages/order/list.vue
new file mode 100644
index 00000000..c1401d05
--- /dev/null
+++ b/pages/order/list.vue
@@ -0,0 +1,585 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 优惠:¥
+ {{ order.total_discount_fee }}
+
+
+ 运费:¥
+ {{ order.dispatch_amount }}
+
+
+ 总金额:
+ ¥{{ order.order_amount }}
+ +
+
+
+ {{ order.score_amount }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/pay/components/account-info-modal.vue b/pages/pay/components/account-info-modal.vue
new file mode 100644
index 00000000..7b33280d
--- /dev/null
+++ b/pages/pay/components/account-info-modal.vue
@@ -0,0 +1,237 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/pay/components/account-type-select.vue b/pages/pay/components/account-type-select.vue
new file mode 100644
index 00000000..14824e35
--- /dev/null
+++ b/pages/pay/components/account-type-select.vue
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/pay/components/add-alipay.vue b/pages/pay/components/add-alipay.vue
new file mode 100644
index 00000000..2f181075
--- /dev/null
+++ b/pages/pay/components/add-alipay.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/pay/components/transfter-balance.vue b/pages/pay/components/transfter-balance.vue
new file mode 100644
index 00000000..691c3212
--- /dev/null
+++ b/pages/pay/components/transfter-balance.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+ ¥
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/pay/index.vue b/pages/pay/index.vue
new file mode 100644
index 00000000..e846025d
--- /dev/null
+++ b/pages/pay/index.vue
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+ 选择支付方式
+
+
+
+
+ {{ item.title }}
+
+
+
+ 余额: {{ userInfo.money }}元
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/pay/recharge-log.vue b/pages/pay/recharge-log.vue
new file mode 100644
index 00000000..2361f61a
--- /dev/null
+++ b/pages/pay/recharge-log.vue
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+ 充值金额
+ {{ item.pay_fee }}元
+
+
+ 支付状态
+ {{ item.status_text }}
+
+
+ 充值渠道
+ {{ item.platform_text }}
+
+
+ 充值单号
+ {{ item.order_sn }}
+
+
+ 充值时间
+ {{ item.paid_time }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/pay/recharge.vue b/pages/pay/recharge.vue
new file mode 100644
index 00000000..f134afdf
--- /dev/null
+++ b/pages/pay/recharge.vue
@@ -0,0 +1,264 @@
+
+
+
+
+ 当前余额(元)
+ {{ userInfo.money }}
+
+
+
+
+
+ 充值金额
+
+ ¥
+
+
+
+
+
+
+
+
+ 关闭充值
+
+
+
+
+
+
+
diff --git a/pages/pay/result.vue b/pages/pay/result.vue
new file mode 100644
index 00000000..8ed7b41f
--- /dev/null
+++ b/pages/pay/result.vue
@@ -0,0 +1,256 @@
+
+
+
+
+
+
+
+ 支付成功
+ 支付失败
+ 该订单已关闭
+ 检测支付结果...
+
+ ¥{{ state.orderInfo.pay_fee }}
+ +
+
+
+ {{ state.orderInfo.score_amount }}
+
+
+
+
+
+
+
+
+
+
+
+ 获取实时发货信息与订单状态
+ 立即订阅
+
+
+
+
+
+
+
+
+
diff --git a/pages/pay/withdraw-log.vue b/pages/pay/withdraw-log.vue
new file mode 100644
index 00000000..3661d4d9
--- /dev/null
+++ b/pages/pay/withdraw-log.vue
@@ -0,0 +1,187 @@
+
+
+
+
+
+
+ {{
+ item.withdraw_type === 'bank'
+ ? '提现至银行卡'
+ : item.withdraw_type === 'alipay'
+ ? '提现至支付宝'
+ : '提现至微信'
+ }}
+ {{ item.amount }}元
+
+
+ 申请状态
+ {{ item.status_text }}
+
+
+ 账户信息
+ {{ item.withdraw_info_hidden.开户行 }}[{{ item.withdraw_info_hidden.银行卡号 }}]
+
+ 支付宝[{{ item.withdraw_info_hidden.支付宝账户 }}]
+
+ 微信零钱
+
+
+ 提现单号
+ {{ item.withdraw_sn }}
+
+
+ 手续费
+ {{ item.charge_fee }}元
+
+
+ 申请时间
+ {{ item.create_time }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/pay/withdraw.vue b/pages/pay/withdraw.vue
new file mode 100644
index 00000000..d3b99fd8
--- /dev/null
+++ b/pages/pay/withdraw.vue
@@ -0,0 +1,380 @@
+
+
+
+
+
+ 可提现金额(元)
+ {{ userInfo.commission }}
+
+
+
+
+
+ 提现金额
+
+ ¥
+
+
+
+ 提现至
+
+ 请选择提现方式
+ 微信零钱
+ 支付宝账户
+ 银行卡转账
+
+
+
+
+
+ {{ state.accountInfo.account_header }}|{{ state.accountInfo.account_name }}
+
+ 暂无提现账户
+
+
+
+
+
+
+
+ 提现说明
+
+ {{ index + 1 }}.{{ rule }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/public/error.vue b/pages/public/error.vue
new file mode 100644
index 00000000..a16d4d5c
--- /dev/null
+++ b/pages/public/error.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/public/faq.vue b/pages/public/faq.vue
new file mode 100644
index 00000000..9c9ac6ca
--- /dev/null
+++ b/pages/public/faq.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+ {{ item.content }}
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/public/feedback.vue b/pages/public/feedback.vue
new file mode 100644
index 00000000..2ef62d3a
--- /dev/null
+++ b/pages/public/feedback.vue
@@ -0,0 +1,228 @@
+
+
+
+
+ 请选择类型
+
+
+
+
+
+
+
+ 相关描述
+
+
+
+
+
+
+ 联系方式
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/public/richtext.vue b/pages/public/richtext.vue
new file mode 100644
index 00000000..e294ede9
--- /dev/null
+++ b/pages/public/richtext.vue
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
diff --git a/pages/public/setting.vue b/pages/public/setting.vue
new file mode 100644
index 00000000..c3cfc1f3
--- /dev/null
+++ b/pages/public/setting.vue
@@ -0,0 +1,239 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/public/webview.vue b/pages/public/webview.vue
new file mode 100644
index 00000000..74e9d204
--- /dev/null
+++ b/pages/public/webview.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/pages/user/address/edit.vue b/pages/user/address/edit.vue
new file mode 100644
index 00000000..e8420491
--- /dev/null
+++ b/pages/user/address/edit.vue
@@ -0,0 +1,264 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 设为默认地址
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/user/address/list.vue b/pages/user/address/list.vue
new file mode 100644
index 00000000..7f8a5754
--- /dev/null
+++ b/pages/user/address/list.vue
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/user/goods-collect.vue b/pages/user/goods-collect.vue
new file mode 100644
index 00000000..0c578812
--- /dev/null
+++ b/pages/user/goods-collect.vue
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/user/goods-log.vue b/pages/user/goods-log.vue
new file mode 100644
index 00000000..34b1f201
--- /dev/null
+++ b/pages/user/goods-log.vue
@@ -0,0 +1,288 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/user/info.vue b/pages/user/info.vue
new file mode 100644
index 00000000..5bb6b641
--- /dev/null
+++ b/pages/user/info.vue
@@ -0,0 +1,503 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 第三方账号绑定
+
+
+
+
+ 微信公众号
+
+
+
+ 微信小程序
+
+
+
+ 微信开放平台
+
+
+
+
+ {{ state.thirdOauthInfo.nickname }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/user/invoice/edit.vue b/pages/user/invoice/edit.vue
new file mode 100644
index 00000000..704d2fce
--- /dev/null
+++ b/pages/user/invoice/edit.vue
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/user/invoice/list.vue b/pages/user/invoice/list.vue
new file mode 100644
index 00000000..bb3be36c
--- /dev/null
+++ b/pages/user/invoice/list.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/user/set.vue b/pages/user/set.vue
new file mode 100644
index 00000000..191f074c
--- /dev/null
+++ b/pages/user/set.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/user/wallet/commission.vue b/pages/user/wallet/commission.vue
new file mode 100644
index 00000000..43b2579c
--- /dev/null
+++ b/pages/user/wallet/commission.vue
@@ -0,0 +1,499 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 总收入¥{{ state.pagination.income.toFixed(2) }}
+ 总支出¥{{ (-state.pagination.expense).toFixed(2) }}
+
+
+
+
+
+
+
+
+ 转余额
+ 将您的佣金转到余额中继续消费
+
+ ¥
+
+
+
+
+
+
+
+
+
+
+ {{ item.event_text }}{{ item.memo ? '-' + item.memo : '' }}
+
+ +{{ item.amount }}
+ {{ item.amount }}
+
+
+ {{ item.create_time }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/user/wallet/money.vue b/pages/user/wallet/money.vue
new file mode 100644
index 00000000..476a7f67
--- /dev/null
+++ b/pages/user/wallet/money.vue
@@ -0,0 +1,348 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 总收入¥{{ state.pagination.income.toFixed(2) }}
+ 总支出¥{{ (-state.pagination.expense).toFixed(2) }}
+
+
+
+
+
+
+
+
+
+
+ {{ item.event_text }}{{ item.memo ? '-' + item.memo : '' }}
+
+ +{{ item.amount }}
+ {{ item.amount }}
+
+
+ {{ item.create_time }}
+
+
+
+
+
+
+
+
+
+
diff --git a/pages/user/wallet/score.vue b/pages/user/wallet/score.vue
new file mode 100644
index 00000000..38ddd3df
--- /dev/null
+++ b/pages/user/wallet/score.vue
@@ -0,0 +1,280 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 总收入¥{{ state.pagination.income }}
+ 总支出¥{{ -state.pagination.expense }}
+
+
+
+
+
+
+
+
+
+ {{ item.event_text }}{{ item.memo ? '-' + item.memo : '' }}
+ {{ item.create_time }}
+
+ +{{ parseInt(item.amount) }}
+ {{ parseInt(item.amount) }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/api/activity.js b/sheep/api/activity.js
new file mode 100644
index 00000000..83d620be
--- /dev/null
+++ b/sheep/api/activity.js
@@ -0,0 +1,43 @@
+import request from '@/sheep/request';
+
+export default {
+ myGroupon: (params) =>
+ request({
+ url: 'activity/groupon/myGroupons',
+ method: 'GET',
+ params,
+ }),
+ getGrouponList: (params) =>
+ request({
+ url: 'activity/groupon',
+ method: 'GET',
+ params,
+ }),
+ grouponDetail: (id) =>
+ request({
+ url: 'activity/groupon/' + id,
+ method: 'GET',
+ }),
+ signList: (params) =>
+ request({
+ url: 'activity/signin',
+ method: 'GET',
+ params,
+ }),
+ signAdd: () =>
+ request({
+ url: 'activity/signin',
+ method: 'POST',
+ }),
+ replenish: (data) =>
+ request({
+ url: 'activity/signin/replenish',
+ method: 'POST',
+ data,
+ }),
+ activity: (id) =>
+ request({
+ url: 'activity/activity/' + id,
+ method: 'GET',
+ }),
+};
diff --git a/sheep/api/app.js b/sheep/api/app.js
new file mode 100644
index 00000000..5d279d41
--- /dev/null
+++ b/sheep/api/app.js
@@ -0,0 +1,111 @@
+import request from '@/sheep/request';
+import { baseUrl } from '@/sheep/config';
+
+export default {
+ // 系统初始化
+ init: (templateId) =>
+ request({
+ url: 'init',
+ params: {
+ templateId,
+ },
+ custom: {
+ showError: false,
+ showLoading: false,
+ },
+ }),
+ // 同步客户端页面到后端
+ pageSync: (pages) =>
+ request({
+ url: 'pageSync',
+ method: 'POST',
+ data: {
+ pages,
+ },
+ custom: {
+ showError: false,
+ showLoading: false,
+ },
+ }),
+ // 发送短信
+ sendSms: (data) =>
+ request({
+ url: 'sendSms',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '发送中',
+ },
+ }),
+ //意见反馈
+ feedback: (data) =>
+ request({
+ url: 'feedback',
+ method: 'POST',
+ data,
+ }),
+ // 自定义页面
+ page: (id) =>
+ request({
+ url: 'page/' + id,
+ method: 'GET',
+ }),
+ //积分商城
+ scoreShop: (params) =>
+ request({
+ url: 'app/scoreShop',
+ method: 'GET',
+ params,
+ }),
+ scoreShopIds: (params = {}) =>
+ request({
+ url: 'app/scoreShop/ids',
+ method: 'GET',
+ params,
+ }),
+ scoreShopDetail: (id) =>
+ request({
+ url: 'app/scoreShop/' + id,
+ method: 'GET',
+ }),
+ //上传
+ upload: (file, group = 'ugc', callback) => {
+ const token = uni.getStorageSync('token');
+ uni.showLoading({
+ title: '上传中',
+ });
+ return new Promise((resolve, reject) => {
+ uni.uploadFile({
+ url: baseUrl + '/file/api/upload',
+ filePath: file,
+ name: 'file',
+ formData: {
+ group,
+ },
+ header: {
+ Accept: 'text/json',
+ Authorization: token,
+ },
+ success: (uploadFileRes) => {
+ let result = JSON.parse(uploadFileRes.data);
+ if (result.error === 1) {
+ uni.showToast({
+ icon: 'none',
+ title: result.msg,
+ });
+ } else {
+ return resolve(result.data);
+ }
+ },
+ fail: (error) => {
+ console.log('上传失败:', error);
+ return resolve(false);
+ },
+ complete: () => {
+ uni.hideLoading();
+ },
+ });
+ });
+ },
+};
diff --git a/sheep/api/cart.js b/sheep/api/cart.js
new file mode 100644
index 00000000..efa5c8aa
--- /dev/null
+++ b/sheep/api/cart.js
@@ -0,0 +1,41 @@
+import request from '@/sheep/request';
+
+export default {
+ list: (data) =>
+ request({
+ url: 'cart',
+ method: 'GET',
+ custom: {
+ showLoading: false,
+ auth: true,
+ },
+ }),
+ append: (data) =>
+ request({
+ url: 'cart',
+ method: 'POST',
+ custom: {
+ showSuccess: true,
+ successMsg: '已添加到购物车~',
+ },
+ data: {
+ ...data,
+ type: 'inc',
+ },
+ }),
+ // 删除购物车
+ delete: (ids) =>
+ request({
+ url: 'cart/' + ids,
+ method: 'DELETE',
+ }),
+ update: (data) =>
+ request({
+ url: 'cart',
+ method: 'POST',
+ data: {
+ ...data,
+ type: 'cover',
+ },
+ }),
+};
diff --git a/sheep/api/category.js b/sheep/api/category.js
new file mode 100644
index 00000000..742f9c0c
--- /dev/null
+++ b/sheep/api/category.js
@@ -0,0 +1,10 @@
+import request from '@/sheep/request';
+
+export default {
+ list: (params) =>
+ request({
+ url: 'category',
+ method: 'GET',
+ params,
+ }),
+};
diff --git a/sheep/api/chat.js b/sheep/api/chat.js
new file mode 100644
index 00000000..5a4a544d
--- /dev/null
+++ b/sheep/api/chat.js
@@ -0,0 +1,13 @@
+import request from '@/sheep/request';
+
+export default {
+ // 获取聊天token
+ unifiedToken: () =>
+ request({
+ url: 'unifiedToken',
+ custom: {
+ showError: false,
+ showLoading: false,
+ },
+ }),
+};
diff --git a/sheep/api/commission.js b/sheep/api/commission.js
new file mode 100644
index 00000000..e3b44c75
--- /dev/null
+++ b/sheep/api/commission.js
@@ -0,0 +1,65 @@
+import request from '@/sheep/request';
+
+export default {
+ // 分销商详情
+ agent: () =>
+ request({
+ url: 'commission/agent',
+ method: 'GET',
+ custom: {
+ showLoading: false,
+ showError: false,
+ },
+ }),
+ // 分销表单
+ form: () =>
+ request({
+ url: 'commission/agent/form',
+ method: 'GET',
+ }),
+ // 申请分销商
+ apply: (data) =>
+ request({
+ url: 'commission/agent/apply',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ },
+ }),
+ // 分销动态
+ log: (params) =>
+ request({
+ url: 'commission/log',
+ method: 'GET',
+ params,
+ }),
+ // 分销订单
+ order: (params) =>
+ request({
+ url: 'commission/order',
+ method: 'GET',
+ params,
+ }),
+ // 分销商品
+ goods: (params) =>
+ request({
+ url: 'commission/goods',
+ method: 'GET',
+ params,
+ }),
+ // 我的团队
+ team: (params) =>
+ request({
+ url: 'commission/agent/team',
+ method: 'GET',
+ params,
+ }),
+ // 佣金转余额
+ transfer: (data) =>
+ request({
+ url: 'commission/agent/transfer',
+ method: 'POST',
+ data,
+ }),
+};
diff --git a/sheep/api/coupon.js b/sheep/api/coupon.js
new file mode 100644
index 00000000..4c4d21bc
--- /dev/null
+++ b/sheep/api/coupon.js
@@ -0,0 +1,38 @@
+import request from '@/sheep/request';
+
+export default {
+ // 我的拼团
+ list: (params) =>
+ request({
+ url: 'coupon',
+ method: 'GET',
+ params,
+ custom: {
+ showLoading: false,
+ },
+ }),
+ userCoupon: (params) =>
+ request({
+ url: 'user/coupon',
+ method: 'GET',
+ params,
+ }),
+ detail: (id, user_coupon_id) =>
+ request({
+ url: 'coupon/' + id,
+ method: 'GET',
+ params: {
+ user_coupon_id,
+ },
+ }),
+ get: (id) =>
+ request({
+ url: 'coupon/get/' + id,
+ method: 'POST',
+ }),
+ listByGoods: (id) =>
+ request({
+ url: 'coupon/listByGoods/' + id,
+ method: 'GET',
+ }),
+};
diff --git a/sheep/api/data.js b/sheep/api/data.js
new file mode 100644
index 00000000..0863a85d
--- /dev/null
+++ b/sheep/api/data.js
@@ -0,0 +1,19 @@
+import request from '@/sheep/request';
+
+export default {
+ area: () =>
+ request({
+ url: 'data/area',
+ method: 'GET',
+ }),
+ faq: () =>
+ request({
+ url: 'data/faq',
+ method: 'GET',
+ }),
+ richtext: (id) =>
+ request({
+ url: 'data/richtext/' + id,
+ method: 'GET',
+ }),
+};
diff --git a/sheep/api/goods.js b/sheep/api/goods.js
new file mode 100644
index 00000000..72b0a804
--- /dev/null
+++ b/sheep/api/goods.js
@@ -0,0 +1,79 @@
+import request from '@/sheep/request';
+
+export default {
+ // 商品详情
+ detail: (id, params = {}) =>
+ request({
+ url: 'goods/goods/' + id,
+ method: 'GET',
+ params,
+ custom: {
+ showLoading: false,
+ showError: false,
+ },
+ }),
+
+ // 商品列表
+ list: (params) =>
+ request({
+ url: 'goods/goods',
+ method: 'GET',
+ params,
+ custom: {
+ showLoading: false,
+ showError: false,
+ },
+ }),
+
+ // 商品查询
+ ids: (params = {}) =>
+ request({
+ url: 'goods/goods/ids',
+ method: 'GET',
+ params,
+ custom: {
+ showLoading: false,
+ showError: false,
+ },
+ }),
+
+ // 商品评价列表
+ comment: (id, params = {}) =>
+ request({
+ url: 'goods/comment/' + id,
+ method: 'GET',
+ params,
+ custom: {
+ showLoading: false,
+ showError: false,
+ },
+ }),
+ // 商品评价类型
+ getType: (id) =>
+ request({
+ url: 'goods/comment/getType/' + id,
+ method: 'GET',
+ custom: {
+ showLoading: false,
+ showError: false,
+ },
+ }),
+ // 活动商品查询
+ // 商品查询
+ activity: (params = {}) =>
+ request({
+ url: 'goods/goods/activity',
+ method: 'GET',
+ params,
+ custom: {
+ showLoading: false,
+ showError: false,
+ },
+ }),
+ activityList: (params = {}) =>
+ request({
+ url: 'goods/goods/activityList',
+ method: 'GET',
+ params,
+ }),
+};
diff --git a/sheep/api/index.js b/sheep/api/index.js
new file mode 100644
index 00000000..c25a3493
--- /dev/null
+++ b/sheep/api/index.js
@@ -0,0 +1,10 @@
+const files = import.meta.globEager('./*.js');
+let api = {};
+Object.keys(files).forEach((key) => {
+ api = {
+ ...api,
+ [key.replace(/(.*\/)*([^.]+).*/gi, '$2')]: files[key].default,
+ };
+});
+
+export default api;
diff --git a/sheep/api/order.js b/sheep/api/order.js
new file mode 100644
index 00000000..3bd66ae4
--- /dev/null
+++ b/sheep/api/order.js
@@ -0,0 +1,130 @@
+import request from '@/sheep/request';
+
+export default {
+ // 订单详情
+ detail: (id) =>
+ request({
+ url: 'order/order/' + id,
+ method: 'GET',
+ }),
+ // 发票详情
+ invoice: (id) =>
+ request({
+ url: 'order/invoice/' + id,
+ method: 'GET',
+ }),
+ // 获取支付结果
+ payResult: (id) =>
+ request({
+ url: 'order/order/' + id,
+ method: 'GET',
+ custom: {
+ showLoading: false,
+ },
+ }),
+ // 订单列表
+ list: (params) =>
+ request({
+ url: 'order/order',
+ method: 'GET',
+ params,
+ custom: {
+ showLoading: false,
+ },
+ }),
+ // 计算订单信息
+ calc: (data) =>
+ request({
+ url: 'order/order/calc',
+ method: 'POST',
+ data,
+ }),
+ // 创建订单
+ create: (data) =>
+ request({
+ url: 'order/order/create',
+ method: 'POST',
+ data,
+ }),
+ //订单可用优惠券
+ coupons: (data) =>
+ request({
+ url: 'order/order/coupons',
+ method: 'POST',
+ data,
+ }),
+ // 确认收货
+ confirm: (id) =>
+ request({
+ url: 'order/order/confirm/' + id,
+ method: 'PUT',
+ }),
+ // 评价订单
+ comment: (id, data) =>
+ request({
+ url: 'order/order/comment/' + id,
+ method: 'POST',
+ data,
+ }),
+ // 申请退款
+ applyRefund: (id) =>
+ request({
+ url: 'order/order/applyRefund/' + id,
+ method: 'PUT',
+ }),
+ // 取消订单
+ cancel: (id) =>
+ request({
+ url: 'order/order/cancel/' + id,
+ method: 'PUT',
+ }),
+ // 删除订单
+ delete: (id) =>
+ request({
+ url: 'order/order/' + id,
+ method: 'DELETE',
+ }),
+ // 售后
+ aftersale: {
+ // 申请售后
+ apply: (data) =>
+ request({
+ url: 'order/aftersale',
+ method: 'POST',
+ data,
+ }),
+ list: (params) =>
+ request({
+ url: 'order/aftersale',
+ method: 'GET',
+ params,
+ custom: {
+ showLoading: false,
+ },
+ }),
+ //取消售后
+ cancel: (id) =>
+ request({
+ url: 'order/aftersale/cancel/' + id,
+ method: 'PUT',
+ }),
+ //删除售后单
+ delete: (id) =>
+ request({
+ url: 'order/aftersale/' + id,
+ method: 'DELETE',
+ }),
+ // 售后详情
+ detail: (id) =>
+ request({
+ url: 'order/aftersale/' + id,
+ method: 'GET',
+ }),
+ },
+ //订单包裹
+ express: (id, orderId) =>
+ request({
+ url: 'order/express/' + id + `${orderId ? '/' + orderId : ''}`,
+ method: 'GET',
+ }),
+};
diff --git a/sheep/api/pay.js b/sheep/api/pay.js
new file mode 100644
index 00000000..8cee0205
--- /dev/null
+++ b/sheep/api/pay.js
@@ -0,0 +1,44 @@
+import request from '@/sheep/request';
+
+export default {
+ // 预支付
+ prepay: (data) =>
+ request({
+ url: 'pay/prepay',
+ method: 'POST',
+ data,
+ custom: {
+ loadingMsg: '支付中',
+ },
+ }),
+ // 发起提现
+ withdraw: {
+ list: (params) =>
+ request({
+ url: 'withdraw',
+ method: 'GET',
+ params,
+ custom: {
+ auth: true,
+ },
+ }),
+ rules: () =>
+ request({
+ url: 'withdraw/rules',
+ method: 'GET',
+ custom: {
+ auth: true,
+ },
+ }),
+ apply: (data) =>
+ request({
+ url: 'withdraw/apply',
+ method: 'POST',
+ data,
+ custom: {
+ loadingMsg: '申请中',
+ auth: true,
+ },
+ }),
+ },
+};
diff --git a/sheep/api/third.js b/sheep/api/third.js
new file mode 100644
index 00000000..72b83bff
--- /dev/null
+++ b/sheep/api/third.js
@@ -0,0 +1,127 @@
+import request from '@/sheep/request';
+import { baseUrl, apiPath } from '@/sheep/config';
+
+export default {
+ // 微信相关
+ wechat: {
+ // 第三方登录
+ login: (data) =>
+ request({
+ url: 'third/wechat/login',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '登陆中',
+ },
+ }),
+
+ // 绑定微信
+ bind: (data) =>
+ request({
+ url: 'third/wechat/bind',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '绑定中',
+ },
+ }),
+
+ // 解除绑定微信
+ unbind: (data) =>
+ request({
+ url: 'third/wechat/unbind',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '解除绑定',
+ },
+ }),
+
+ // 公众号授权
+ oauthLogin: (data) =>
+ request({
+ url: 'third/wechat/oauthLogin',
+ method: 'GET',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '登陆中',
+ },
+ }),
+
+ // 获取小程序sessionKey(后端不会给前端返回真实的sessionKey)
+ getSessionId: (data) =>
+ request({
+ url: 'third/wechat/getSessionId',
+ method: 'POST',
+ data,
+ custom: {
+ showLoading: false,
+ },
+ }),
+
+ // 微信小程序 绑定一键获取的手机号
+ bindUserPhoneNumber: (data) =>
+ request({
+ url: 'third/wechat/bindUserPhoneNumber',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '获取中',
+ },
+ }),
+
+ // 网页jssdk
+ jssdk: (data) =>
+ request({
+ url: 'third/wechat/jssdk',
+ method: 'GET',
+ data,
+ custom: {
+ showError: false,
+ showLoading: false,
+ },
+ }),
+
+ // 小程序订阅消息
+ subscribeTemplate: (params) =>
+ request({
+ url: 'third/wechat/subscribeTemplate',
+ method: 'GET',
+ params: {
+ platform: 'miniProgram',
+ },
+ custom: {
+ showError: false,
+ showLoading: false,
+ },
+ }),
+
+ // 获取微信小程序码
+ getWxacode: (path) =>
+ `${baseUrl}${apiPath}third/wechat/wxacode?platform=miniProgram&payload=${encodeURIComponent(
+ JSON.stringify({
+ path,
+ }),
+ )}`,
+ },
+
+ // 苹果相关
+ apple: {
+ // 第三方登录
+ login: (data) =>
+ request({
+ url: 'third/apple/login',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '登陆中',
+ },
+ }),
+ },
+};
diff --git a/sheep/api/trade.js b/sheep/api/trade.js
new file mode 100644
index 00000000..af9c335d
--- /dev/null
+++ b/sheep/api/trade.js
@@ -0,0 +1,37 @@
+import request from '@/sheep/request';
+
+export default {
+ order: (id) =>
+ request({
+ url: 'trade/order/' + id,
+ method: 'GET',
+ custom: {
+ showLoading: false,
+ },
+ }),
+ orderLog: (params) =>
+ request({
+ url: 'trade/order',
+ method: 'GET',
+ params,
+ custom: {
+ showLoading: false,
+ },
+ }),
+
+ rechargeRules: () =>
+ request({
+ url: 'trade/order/rechargeRules',
+ method: 'GET',
+ custom: {
+ showError: false,
+ showLoading: false,
+ },
+ }),
+ recharge: (data) =>
+ request({
+ url: 'trade/order/recharge',
+ method: 'POST',
+ data,
+ }),
+};
diff --git a/sheep/api/user.js b/sheep/api/user.js
new file mode 100644
index 00000000..5fe849e6
--- /dev/null
+++ b/sheep/api/user.js
@@ -0,0 +1,323 @@
+import request from '@/sheep/request';
+import $platform from '@/sheep/platform';
+
+export default {
+ profile: () =>
+ request({
+ url: '/user/api/user/profile',
+ method: 'GET',
+ custom: {
+ showLoading: false,
+ auth: true,
+ },
+ }),
+ update: (data) =>
+ request({
+ url: '/user/api/user/update',
+ method: 'POST',
+ custom: {
+ showSuccess: true,
+ auth: true,
+ },
+ data,
+ }),
+ // 账号登录
+ accountLogin: (data) =>
+ request({
+ url: '/user/api/user/accountLogin',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '登录中',
+ },
+ }),
+ // 短信登录
+ smsLogin: (data) =>
+ request({
+ url: '/user/api/user/smsLogin',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '登录中',
+ },
+ }),
+ // 短信注册
+ smsRegister: (data) =>
+ request({
+ url: '/user/api/user/smsRegister',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '正在注册',
+ },
+ }),
+ // 重置密码
+ resetPassword: (data) =>
+ request({
+ url: '/user/api/user/resetPassword',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '验证中',
+ },
+ }),
+
+ // 修改密码
+ changePassword: (data) =>
+ request({
+ url: '/user/api/user/changePassword',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '验证中',
+ },
+ }),
+
+ // 绑定、更换手机号
+ changeMobile: (data) =>
+ request({
+ url: '/user/api/user/changeMobile',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '验证中',
+ },
+ }),
+
+ // 修改用户名
+ changeUsername: (data) =>
+ request({
+ url: '/user/api/user/changeUsername',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ loadingMsg: '验证中',
+ },
+ }),
+
+ // 第三方授权信息
+ thirdOauthInfo: () =>
+ request({
+ url: '/user/api/user/thirdOauth',
+ method: 'GET',
+ params: {
+ provider: $platform.provider,
+ platform: $platform.platform,
+ },
+ custom: {
+ showLoading: false,
+ },
+ }),
+
+ // 添加分享记录
+ addShareLog: (data) =>
+ request({
+ url: 'share/add',
+ method: 'POST',
+ data,
+ custom: {
+ showError: false,
+ },
+ }),
+ share: {
+ list: (params) =>
+ request({
+ url: 'share/list',
+ method: 'GET',
+ params,
+ }),
+ },
+ // 账号登出
+ logout: (data) =>
+ request({
+ url: '/user/api/user/logout',
+ method: 'POST',
+ data,
+ }),
+ // 账号注销
+ logoff: (data) =>
+ request({
+ url: '/user/api/user/logoff',
+ method: 'POST',
+ data,
+ }),
+
+ address: {
+ default: () =>
+ request({
+ url: 'user/address/default',
+ method: 'GET',
+ custom: {
+ showError: false,
+ },
+ }),
+ list: () =>
+ request({
+ url: 'user/address',
+ method: 'GET',
+ custom: {},
+ }),
+ create: (data) =>
+ request({
+ url: 'user/address',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ },
+ }),
+ update: (id, data) =>
+ request({
+ url: 'user/address/' + id,
+ method: 'PUT',
+ data,
+ custom: {
+ showSuccess: true,
+ },
+ }),
+ detail: (id) =>
+ request({
+ url: 'user/address/' + id,
+ method: 'GET',
+ }),
+ delete: (id) =>
+ request({
+ url: 'user/address/' + id,
+ method: 'DELETE',
+ }),
+ },
+ invoice: {
+ list: () =>
+ request({
+ url: 'user/invoice',
+ method: 'GET',
+ custom: {},
+ }),
+ create: (data) =>
+ request({
+ url: 'user/invoice',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ },
+ }),
+ update: (id, data) =>
+ request({
+ url: 'user/invoice/' + id,
+ method: 'PUT',
+ data,
+ custom: {
+ showSuccess: true,
+ },
+ }),
+ detail: (id) =>
+ request({
+ url: 'user/invoice/' + id,
+ method: 'GET',
+ }),
+ delete: (id) =>
+ request({
+ url: 'user/invoice/' + id,
+ method: 'DELETE',
+ }),
+ },
+ favorite: {
+ list: (params) =>
+ request({
+ url: 'user/goodsLog/favorite',
+ method: 'GET',
+ params,
+ }),
+ do: (id) =>
+ request({
+ url: 'user/goodsLog/favorite',
+ method: 'POST',
+ data: {
+ goods_id: id,
+ },
+ custom: {
+ showSuccess: true,
+ auth: true,
+ },
+ }),
+ cancel: (id) =>
+ request({
+ url: 'user/goodsLog/favorite',
+ method: 'POST',
+ data: {
+ goods_ids: id,
+ },
+ custom: {
+ showSuccess: true,
+ auth: true,
+ },
+ }),
+ },
+ view: {
+ list: (params) =>
+ request({
+ url: 'user/goodsLog/views',
+ method: 'GET',
+ params,
+ custom: {},
+ }),
+ delete: (data) =>
+ request({
+ url: 'user/goodsLog/viewDel',
+ method: 'DELETE',
+ data,
+ custom: {
+ showSuccess: true,
+ },
+ }),
+ },
+ wallet: {
+ log: (params) =>
+ request({
+ url: '/user/api/walletLog',
+ method: 'GET',
+ params,
+ custom: {},
+ }),
+ },
+ account: {
+ info: (params) =>
+ request({
+ url: 'user/account',
+ method: 'GET',
+ params,
+ custom: {
+ showError: false,
+ auth: true,
+ },
+ }),
+ save: (data) =>
+ request({
+ url: 'user/account',
+ method: 'POST',
+ data,
+ custom: {
+ showSuccess: true,
+ auth: true,
+ },
+ }),
+ },
+ //数量接口
+ data: () =>
+ request({
+ url: 'user/user/data',
+ method: 'GET',
+ custom: {
+ showLoading: false,
+ auth: true,
+ },
+ }),
+};
diff --git a/sheep/components/s-activity-pop/s-activity-pop.vue b/sheep/components/s-activity-pop/s-activity-pop.vue
new file mode 100644
index 00000000..551053db
--- /dev/null
+++ b/sheep/components/s-activity-pop/s-activity-pop.vue
@@ -0,0 +1,96 @@
+
+
+
+ 营销活动
+
+
+
+
+ {{ item.type_text }}
+
+
+ {{ text }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-address-item/s-address-item.vue b/sheep/components/s-address-item/s-address-item.vue
new file mode 100644
index 00000000..cdb2d8c5
--- /dev/null
+++ b/sheep/components/s-address-item/s-address-item.vue
@@ -0,0 +1,105 @@
+
+
+
+
+
+ {{ props.item.province_name }} {{ props.item.city_name }} {{ props.item.district_name }}
+
+ {{ props.item.address }}
+ {{ props.item.consignee }} {{ props.item.mobile }}
+
+ 请选择收货地址
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-auth-modal/components/account-login.vue b/sheep/components/s-auth-modal/components/account-login.vue
new file mode 100644
index 00000000..4d409428
--- /dev/null
+++ b/sheep/components/s-auth-modal/components/account-login.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+ 账号登录
+
+ 短信登录
+
+
+ 如果未设置过密码,请点击忘记密码
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-auth-modal/components/change-mobile.vue b/sheep/components/s-auth-modal/components/change-mobile.vue
new file mode 100644
index 00000000..2241173d
--- /dev/null
+++ b/sheep/components/s-auth-modal/components/change-mobile.vue
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+ {{ userInfo.verification.mobile ? '更换手机号' : '绑定手机号' }}
+
+ 为了您的账号安全,请使用本人手机号码
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-auth-modal/components/change-password.vue b/sheep/components/s-auth-modal/components/change-password.vue
new file mode 100644
index 00000000..0e0af5cc
--- /dev/null
+++ b/sheep/components/s-auth-modal/components/change-password.vue
@@ -0,0 +1,121 @@
+
+
+
+
+
+ 修改密码
+ 如密码丢失或未设置,请点击忘记密码重新设置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-auth-modal/components/change-username.vue b/sheep/components/s-auth-modal/components/change-username.vue
new file mode 100644
index 00000000..82d3de95
--- /dev/null
+++ b/sheep/components/s-auth-modal/components/change-username.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+ 修改用户名
+ 用户名仅限修改一次
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-auth-modal/components/reset-password.vue b/sheep/components/s-auth-modal/components/reset-password.vue
new file mode 100644
index 00000000..81858ad9
--- /dev/null
+++ b/sheep/components/s-auth-modal/components/reset-password.vue
@@ -0,0 +1,114 @@
+
+
+
+
+
+ 重置密码
+ 为了您的账号安全,设置密码前请先进行安全验证
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-auth-modal/components/sms-login.vue b/sheep/components/s-auth-modal/components/sms-login.vue
new file mode 100644
index 00000000..284d0da1
--- /dev/null
+++ b/sheep/components/s-auth-modal/components/sms-login.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+ 账号登录
+ 短信登录
+
+ 未注册手机号请先点击下方立即注册
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-auth-modal/components/sms-register.vue b/sheep/components/s-auth-modal/components/sms-register.vue
new file mode 100644
index 00000000..8aaba46d
--- /dev/null
+++ b/sheep/components/s-auth-modal/components/sms-register.vue
@@ -0,0 +1,126 @@
+
+
+
+
+
+ 注册
+ 请使用本人手机号完成注册
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-auth-modal/index.scss b/sheep/components/s-auth-modal/index.scss
new file mode 100644
index 00000000..c4424e73
--- /dev/null
+++ b/sheep/components/s-auth-modal/index.scss
@@ -0,0 +1,151 @@
+@keyframes title-animation {
+ 0% {
+ font-size: 32rpx;
+ }
+ 100% {
+ font-size: 36rpx;
+ }
+}
+
+.login-wrap {
+ padding: 50rpx 34rpx;
+ min-height: 500rpx;
+ background-color: #fff;
+ border-radius: 20rpx 20rpx 0 0;
+}
+
+.head-box {
+ .head-title {
+ min-width: 160rpx;
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #333333;
+ line-height: 36rpx;
+ }
+ .head-title-active {
+ width: 160rpx;
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #999;
+ line-height: 36rpx;
+ }
+ .head-title-animation {
+ animation-name: title-animation;
+ animation-duration: 0.1s;
+ animation-timing-function: ease-out;
+ animation-fill-mode: forwards;
+ }
+ .head-title-line {
+ position: relative;
+ &::before {
+ content: '';
+ width: 1rpx;
+ height: 34rpx;
+ background-color: #e4e7ed;
+ position: absolute;
+ left: -30rpx;
+ top: 50%;
+ transform: translateY(-50%);
+ }
+ }
+ .head-subtitle {
+ font-size: 26rpx;
+ font-weight: 400;
+ color: #afb6c0;
+ text-align: left;
+ display: flex;
+ }
+}
+
+// .code-btn[disabled] {
+// background-color: #fff;
+// }
+.code-btn-start {
+ width: 160rpx;
+ height: 56rpx;
+ line-height: normal;
+ border: 2rpx solid var(--ui-BG-Main);
+ border-radius: 28rpx;
+ font-size: 26rpx;
+ font-weight: 400;
+ color: var(--ui-BG-Main);
+ opacity: 1;
+}
+
+.forgot-btn {
+ width: 160rpx;
+ line-height: 56rpx;
+ font-size: 30rpx;
+ font-weight: 500;
+ color: #999;
+}
+
+.login-btn-start {
+ width: 158rpx;
+ height: 56rpx;
+ line-height: normal;
+ background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
+ border-radius: 28rpx;
+ font-size: 26rpx;
+ font-weight: 500;
+ color: #fff;
+}
+
+.type-btn {
+ padding: 20rpx;
+ margin: 40rpx auto;
+ width: 200rpx;
+ font-size: 30rpx;
+ font-weight: 500;
+ color: #999999;
+}
+
+.auto-login-box {
+ width: 100%;
+ .auto-login-btn {
+ width: 68rpx;
+ height: 68rpx;
+ border-radius: 50%;
+ margin: 0 30rpx;
+ }
+ .auto-login-img {
+ width: 68rpx;
+ height: 68rpx;
+ border-radius: 50%;
+ }
+}
+
+.agreement-box {
+ margin: 80rpx auto 0;
+ .protocol-check {
+ transform: scale(0.7);
+ }
+ .agreement-text {
+ font-size: 26rpx;
+ font-weight: 500;
+ color: #999999;
+ .tcp-text {
+ color: var(--ui-BG-Main);
+ }
+ }
+}
+
+// 修改密码
+.editPwd-btn-box {
+ .save-btn {
+ width: 690rpx;
+ line-height: 70rpx;
+ background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
+ border-radius: 35rpx;
+ font-size: 28rpx;
+ font-weight: 500;
+ color: #ffffff;
+ }
+ .forgot-btn {
+ width: 690rpx;
+ line-height: 70rpx;
+ font-size: 28rpx;
+ font-weight: 500;
+ color: #999999;
+ }
+}
diff --git a/sheep/components/s-auth-modal/s-auth-modal.vue b/sheep/components/s-auth-modal/s-auth-modal.vue
new file mode 100644
index 00000000..2868ba2c
--- /dev/null
+++ b/sheep/components/s-auth-modal/s-auth-modal.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-block-item/s-block-item.vue b/sheep/components/s-block-item/s-block-item.vue
new file mode 100644
index 00000000..49a159da
--- /dev/null
+++ b/sheep/components/s-block-item/s-block-item.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-block/s-block.vue b/sheep/components/s-block/s-block.vue
new file mode 100644
index 00000000..529839cb
--- /dev/null
+++ b/sheep/components/s-block/s-block.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
diff --git a/sheep/components/s-coupon-block/s-coupon-block.vue b/sheep/components/s-coupon-block/s-coupon-block.vue
new file mode 100644
index 00000000..c5b2f2f8
--- /dev/null
+++ b/sheep/components/s-coupon-block/s-coupon-block.vue
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-coupon-card/s-coupon-card.vue b/sheep/components/s-coupon-card/s-coupon-card.vue
new file mode 100644
index 00000000..2e0d2209
--- /dev/null
+++ b/sheep/components/s-coupon-card/s-coupon-card.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
diff --git a/sheep/components/s-coupon-get/s-coupon-get.vue b/sheep/components/s-coupon-get/s-coupon-get.vue
new file mode 100644
index 00000000..bfa9b9bb
--- /dev/null
+++ b/sheep/components/s-coupon-get/s-coupon-get.vue
@@ -0,0 +1,108 @@
+
+
+
+ 优惠券
+
+ 可使用优惠券
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-coupon-list/s-coupon-list.vue b/sheep/components/s-coupon-list/s-coupon-list.vue
new file mode 100644
index 00000000..ed29b092
--- /dev/null
+++ b/sheep/components/s-coupon-list/s-coupon-list.vue
@@ -0,0 +1,184 @@
+
+
+
+ {{ data.type_text }}
+
+
+ {{ data.name }}
+
+
+ ¥
+ {{ data.amount }}
+ 折
+
+
+
+
+
+ {{
+ type === 'user'
+ ? '有效期:' + data.use_start_time.substring(0, 11)
+ : '领取时间:' + data.get_start_time.substring(0, 11)
+ }}至
+ {{
+ type === 'user'
+ ? data.use_end_time.substring(0, 11)
+ : data.get_end_time.substring(0, 11)
+ }}
+
+ 满{{ data.enough }}可用
+
+
+
+
+
+
+ {{ data.description }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-coupon-select/s-coupon-select.vue b/sheep/components/s-coupon-select/s-coupon-select.vue
new file mode 100644
index 00000000..7a4f3e25
--- /dev/null
+++ b/sheep/components/s-coupon-select/s-coupon-select.vue
@@ -0,0 +1,109 @@
+
+
+
+ 优惠券
+
+ 可使用优惠券
+
+
+
+
+
+
+
+ 不可使用优惠券
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-custom-navbar/components/navbar-item.vue b/sheep/components/s-custom-navbar/components/navbar-item.vue
new file mode 100644
index 00000000..b8431a0f
--- /dev/null
+++ b/sheep/components/s-custom-navbar/components/navbar-item.vue
@@ -0,0 +1,62 @@
+
+
+
+ {{ data.text }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-custom-navbar/components/navbar.vue b/sheep/components/s-custom-navbar/components/navbar.vue
new file mode 100644
index 00000000..36050ec4
--- /dev/null
+++ b/sheep/components/s-custom-navbar/components/navbar.vue
@@ -0,0 +1,314 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-custom-navbar/s-custom-navbar.vue b/sheep/components/s-custom-navbar/s-custom-navbar.vue
new file mode 100644
index 00000000..561e9924
--- /dev/null
+++ b/sheep/components/s-custom-navbar/s-custom-navbar.vue
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-discount-list/s-discount-list.vue b/sheep/components/s-discount-list/s-discount-list.vue
new file mode 100644
index 00000000..22fbd43c
--- /dev/null
+++ b/sheep/components/s-discount-list/s-discount-list.vue
@@ -0,0 +1,114 @@
+
+
+
+ 活动优惠
+
+
+
+ 共{{ item.goods_ids.length }}件,
+
+ 满{{ item.discount_rule.full }}打{{ item.discount_rule.discount }}折,已减
+
+ 满赠
+
+ 满{{ item.discount_rule.full }}减{{ item.discount_rule.discount }},已减
+
+ ¥{{ item.promo_discount_money || '0.00' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-empty/s-empty.vue b/sheep/components/s-empty/s-empty.vue
new file mode 100644
index 00000000..647d3af1
--- /dev/null
+++ b/sheep/components/s-empty/s-empty.vue
@@ -0,0 +1,93 @@
+
+
+
+
+ {{ text }}
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-float-menu/s-float-menu.vue b/sheep/components/s-float-menu/s-float-menu.vue
new file mode 100644
index 00000000..b696c34b
--- /dev/null
+++ b/sheep/components/s-float-menu/s-float-menu.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-goods-card/s-goods-card.vue b/sheep/components/s-goods-card/s-goods-card.vue
new file mode 100644
index 00000000..8aeba1f1
--- /dev/null
+++ b/sheep/components/s-goods-card/s-goods-card.vue
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-goods-column/s-goods-column.vue b/sheep/components/s-goods-column/s-goods-column.vue
new file mode 100644
index 00000000..1225884b
--- /dev/null
+++ b/sheep/components/s-goods-column/s-goods-column.vue
@@ -0,0 +1,711 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ data.title }}
+
+
+ {{ priceUnit }}
+ {{ isArray(data.price) ? data.price[0] : data.price }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ data.title }}
+
+
+ {{ priceUnit }}
+ {{ isArray(data.price) ? data.price[0] : data.price }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ data.title }}
+
+
+ {{ data.subtitle }}
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+ {{ priceUnit }}
+ {{ isArray(data.price) ? data.price[0] : data.price }}
+
+
+
+ {{ priceUnit }}
+ {{ data.original_price }}
+
+
+
+
+ {{ salesAndStock }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 秒杀
+
+ 拼团
+
+
+
+
+
+ {{ data.title }}
+
+
+ {{ data.subtitle }}
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+ {{ priceUnit }}
+ {{ isArray(data.price) ? data.price[0] : data.price }}
+
+
+ {{ priceUnit }}
+ {{ data.original_price }}
+
+
+
+ {{ salesAndStock }}
+
+
+
+
+ 去购买
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ data.title }}
+
+
+ {{ data.subtitle }}
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+ {{ priceUnit }}
+ {{ isArray(data.price) ? data.price[0] : data.price }}
+
+
+ {{ priceUnit }}
+ {{ data.original_price }}
+
+
+
+ {{ salesAndStock }}
+
+
+
+
+ 去购买
+
+
+
+
+
+
+
diff --git a/sheep/components/s-goods-item/s-goods-item.vue b/sheep/components/s-goods-item/s-goods-item.vue
new file mode 100644
index 00000000..af39e18f
--- /dev/null
+++ b/sheep/components/s-goods-item/s-goods-item.vue
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+ {{ title }}
+ {{ skuString }}
+
+
+
+
+
+
+ ¥{{ price }}
+
+ +
+
+
+ {{ score }}
+
+ x {{ num }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-goods-scroll/s-goods-scroll.vue b/sheep/components/s-goods-scroll/s-goods-scroll.vue
new file mode 100644
index 00000000..08c12d42
--- /dev/null
+++ b/sheep/components/s-goods-scroll/s-goods-scroll.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-goods-shelves/s-goods-shelves.vue b/sheep/components/s-goods-shelves/s-goods-shelves.vue
new file mode 100644
index 00000000..06975d34
--- /dev/null
+++ b/sheep/components/s-goods-shelves/s-goods-shelves.vue
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-groupon-block/s-groupon-block.vue b/sheep/components/s-groupon-block/s-groupon-block.vue
new file mode 100644
index 00000000..3aea89c0
--- /dev/null
+++ b/sheep/components/s-groupon-block/s-groupon-block.vue
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-image-banner/s-image-banner.vue b/sheep/components/s-image-banner/s-image-banner.vue
new file mode 100644
index 00000000..546afaab
--- /dev/null
+++ b/sheep/components/s-image-banner/s-image-banner.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
diff --git a/sheep/components/s-image-block/s-image-block.vue b/sheep/components/s-image-block/s-image-block.vue
new file mode 100644
index 00000000..27983f84
--- /dev/null
+++ b/sheep/components/s-image-block/s-image-block.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-image-cube/s-image-cube.vue b/sheep/components/s-image-cube/s-image-cube.vue
new file mode 100644
index 00000000..4d896641
--- /dev/null
+++ b/sheep/components/s-image-cube/s-image-cube.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-invoice-item/s-invoice-item.vue b/sheep/components/s-invoice-item/s-invoice-item.vue
new file mode 100644
index 00000000..3b4e7d9a
--- /dev/null
+++ b/sheep/components/s-invoice-item/s-invoice-item.vue
@@ -0,0 +1,96 @@
+
+
+
+ {{ props.item.name }}
+ {{ props.item.mobile }}
+
+
+ 请选择收货地址
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-layout/s-layout.vue b/sheep/components/s-layout/s-layout.vue
new file mode 100644
index 00000000..5f83a503
--- /dev/null
+++ b/sheep/components/s-layout/s-layout.vue
@@ -0,0 +1,244 @@
+
+
+
+
+ emits('search', e)"
+ :defaultSearch="defaultSearch"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-line-block/s-line-block.vue b/sheep/components/s-line-block/s-line-block.vue
new file mode 100644
index 00000000..b454ef75
--- /dev/null
+++ b/sheep/components/s-line-block/s-line-block.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/sheep/components/s-menu-button/s-menu-button.vue b/sheep/components/s-menu-button/s-menu-button.vue
new file mode 100644
index 00000000..5bf52c3a
--- /dev/null
+++ b/sheep/components/s-menu-button/s-menu-button.vue
@@ -0,0 +1,368 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ state.cur + 1 }} / {{ menuList.length }}
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-menu-grid/s-menu-grid.vue b/sheep/components/s-menu-grid/s-menu-grid.vue
new file mode 100644
index 00000000..e6c385b4
--- /dev/null
+++ b/sheep/components/s-menu-grid/s-menu-grid.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+ {{ item.badge.text }}
+
+
+
+
+
+
+ {{ item.title.text }}
+
+
+ {{ item.tip.text }}
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-menu-list/s-menu-list.vue b/sheep/components/s-menu-list/s-menu-list.vue
new file mode 100644
index 00000000..ecf6b12a
--- /dev/null
+++ b/sheep/components/s-menu-list/s-menu-list.vue
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
diff --git a/sheep/components/s-menu-tools/s-menu-tools.vue b/sheep/components/s-menu-tools/s-menu-tools.vue
new file mode 100644
index 00000000..8ad0d05e
--- /dev/null
+++ b/sheep/components/s-menu-tools/s-menu-tools.vue
@@ -0,0 +1,122 @@
+
+
+
+
+ 快捷菜单
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-notice-block/s-notice-block.vue b/sheep/components/s-notice-block/s-notice-block.vue
new file mode 100644
index 00000000..8ef01b87
--- /dev/null
+++ b/sheep/components/s-notice-block/s-notice-block.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-order-card/s-order-card.vue b/sheep/components/s-order-card/s-order-card.vue
new file mode 100644
index 00000000..f7d8b9f1
--- /dev/null
+++ b/sheep/components/s-order-card/s-order-card.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
diff --git a/sheep/components/s-popup-image/s-popup-image.vue b/sheep/components/s-popup-image/s-popup-image.vue
new file mode 100644
index 00000000..b5be4322
--- /dev/null
+++ b/sheep/components/s-popup-image/s-popup-image.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-richtext-block/s-richtext-block.vue b/sheep/components/s-richtext-block/s-richtext-block.vue
new file mode 100644
index 00000000..aeff20eb
--- /dev/null
+++ b/sheep/components/s-richtext-block/s-richtext-block.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
diff --git a/sheep/components/s-score-block/s-score-block.vue b/sheep/components/s-score-block/s-score-block.vue
new file mode 100644
index 00000000..9472b2ed
--- /dev/null
+++ b/sheep/components/s-score-block/s-score-block.vue
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-score-card/s-score-card.vue b/sheep/components/s-score-card/s-score-card.vue
new file mode 100644
index 00000000..ae2fd59b
--- /dev/null
+++ b/sheep/components/s-score-card/s-score-card.vue
@@ -0,0 +1,459 @@
+
+
+
+
+
+
+
+ {{ data.title }}
+
+
+ {{ data.subtitle }}
+
+
+
+ {{ Number(data.price[0]) > 0 ? '¥' + data.price[0] + '+' : '' }}
+
+ {{ data.score }}
+
+
+
+ {{ priceUnit }}
+ {{ data.original_price }}
+
+
+
+
+ {{ salesAndStock }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ data.title }}
+
+
+ {{ data.subtitle }}
+
+
+
+
+
+ {{ Number(data.price[0]) > 0 ? '¥' + data.price[0] + '+' : '' }}
+
+ {{ data.score }}
+
+
+ {{ priceUnit }}
+ {{ data.original_price }}
+
+
+
+ {{ salesAndStock }}
+
+
+
+
+ 去兑换
+
+
+
+
+
+
+
+
+ {{ data.title }}
+
+
+ {{ data.subtitle }}
+
+
+
+
+
+
+ {{ item.title }}
+
+
+
+
+
+ {{ Number(data.price[0]) > 0 ? '¥' + data.price[0] + '+' : '' }}
+
+ {{ data.score ? data.score : '' }}
+
+
+ ¥
+ {{ data.original_price }}
+
+
+
+ {{ salesAndStock }}
+
+
+
+
+ 去兑换
+
+
+
+
+
+
diff --git a/sheep/components/s-search-block/s-search-block.vue b/sheep/components/s-search-block/s-search-block.vue
new file mode 100644
index 00000000..204a02ee
--- /dev/null
+++ b/sheep/components/s-search-block/s-search-block.vue
@@ -0,0 +1,158 @@
+
+
+
+
+
+ {{ placeholder }}
+
+
+
+
+
+ {{ item.text }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-seckill-block/s-seckill-block.vue b/sheep/components/s-seckill-block/s-seckill-block.vue
new file mode 100644
index 00000000..111be29d
--- /dev/null
+++ b/sheep/components/s-seckill-block/s-seckill-block.vue
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-select-groupon-sku/s-select-groupon-sku.vue b/sheep/components/s-select-groupon-sku/s-select-groupon-sku.vue
new file mode 100644
index 00000000..01d8c173
--- /dev/null
+++ b/sheep/components/s-select-groupon-sku/s-select-groupon-sku.vue
@@ -0,0 +1,569 @@
+
+
+
+
+
+
+
+
+ 拼团人数
+
+
+
+
+
+ {{ sku1.name }}
+
+
+
+
+
+ 购买数量
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-select-seckill-sku/s-select-seckill-sku.vue b/sheep/components/s-select-seckill-sku/s-select-seckill-sku.vue
new file mode 100644
index 00000000..a495653c
--- /dev/null
+++ b/sheep/components/s-select-seckill-sku/s-select-seckill-sku.vue
@@ -0,0 +1,433 @@
+
+
+
+
+
+
+
+
+ {{ sku1.name }}
+
+
+
+
+
+ 购买数量
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-select-sku/s-select-sku.vue b/sheep/components/s-select-sku/s-select-sku.vue
new file mode 100644
index 00000000..df6c2c30
--- /dev/null
+++ b/sheep/components/s-select-sku/s-select-sku.vue
@@ -0,0 +1,460 @@
+
+
+
+
+
+
+
+
+ {{ sku1.name }}
+
+
+
+
+
+ 购买数量
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-share-modal/canvas-poster/index.vue b/sheep/components/s-share-modal/canvas-poster/index.vue
new file mode 100644
index 00000000..5b409f69
--- /dev/null
+++ b/sheep/components/s-share-modal/canvas-poster/index.vue
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-share-modal/canvas-poster/poster/goods.js b/sheep/components/s-share-modal/canvas-poster/poster/goods.js
new file mode 100644
index 00000000..f6378b3d
--- /dev/null
+++ b/sheep/components/s-share-modal/canvas-poster/poster/goods.js
@@ -0,0 +1,119 @@
+import sheep from '@/sheep';
+
+const goods = (poster) => {
+ const width = poster.width;
+ const userInfo = sheep.$store('user').userInfo;
+
+ return {
+ background: sheep.$url.cdn(sheep.$store('app').platform.share.posterInfo.goods_bg),
+ list: [
+ {
+ name: 'nickname',
+ type: 'text',
+ val: userInfo.nickname,
+ x: width * 0.22,
+ y: width * 0.06,
+ paintbrushProps: {
+ fillStyle: '#333',
+ font: {
+ fontSize: 16,
+ fontFamily: 'sans-serif',
+ },
+ },
+ },
+ {
+ name: 'avatar',
+ type: 'image',
+ val: sheep.$url.cdn(userInfo.avatar),
+ x: width * 0.04,
+ y: width * 0.04,
+ width: width * 0.14,
+ height: width * 0.14,
+ d: width * 0.14,
+ },
+ {
+ name: 'goodsImage',
+ type: 'image',
+ val: poster.shareInfo.poster.image,
+ x: width * 0.03,
+ y: width * 0.21,
+ width: width * 0.94,
+ height: width * 0.94,
+ r: 10,
+ },
+ {
+ name: 'goodsTitle',
+ type: 'text',
+ val: poster.shareInfo.poster.title,
+ x: width * 0.04,
+ y: width * 1.18,
+ maxWidth: width * 0.92,
+ lineHeight: 5,
+ paintbrushProps: {
+ fillStyle: '#333',
+ font: {
+ fontSize: 14,
+ },
+ },
+ },
+ {
+ name: 'goodsPrice',
+ type: 'text',
+ val: '¥' + poster.shareInfo.poster.price,
+ x: width * 0.04,
+ y: width * 1.3,
+ paintbrushProps: {
+ fillStyle: '#ff0000',
+ font: {
+ fontSize: 20,
+ fontFamily: 'OPPOSANS',
+ },
+ },
+ },
+ {
+ name: 'goodsOriginalPrice',
+ type: 'text',
+ val:
+ poster.shareInfo.poster.original_price > 0
+ ? '¥' + poster.shareInfo.poster.original_price
+ : '',
+ x: width * 0.3,
+ y: width * 1.32,
+ paintbrushProps: {
+ fillStyle: '#999',
+ font: {
+ fontSize: 10,
+ fontFamily: 'OPPOSANS',
+ },
+ },
+ textDecoration: {
+ line: 'line-through',
+ style: 'solide',
+ },
+ },
+ // #ifndef MP-WEIXIN
+ {
+ name: 'qrcode',
+ type: 'qrcode',
+ val: poster.shareInfo.link,
+ x: width * 0.75,
+ y: width * 1.3,
+ size: width * 0.2,
+ },
+ // #endif
+ // #ifdef MP-WEIXIN
+ {
+ name: 'wxacode',
+ type: 'image',
+ val: sheep.$api.third.wechat.getWxacode(poster.shareInfo.path),
+ x: width * 0.75,
+ y: width * 1.3,
+ width: width * 0.2,
+ height: width * 0.2,
+ },
+ // #endif
+ ],
+ };
+};
+
+export default goods;
diff --git a/sheep/components/s-share-modal/canvas-poster/poster/groupon.js b/sheep/components/s-share-modal/canvas-poster/poster/groupon.js
new file mode 100644
index 00000000..0ed33516
--- /dev/null
+++ b/sheep/components/s-share-modal/canvas-poster/poster/groupon.js
@@ -0,0 +1,112 @@
+import sheep from '@/sheep';
+
+const groupon = (poster) => {
+ const width = poster.width;
+ const userInfo = sheep.$store('user').userInfo;
+
+ return {
+ background: sheep.$url.cdn(sheep.$store('app').platform.share.posterInfo.groupon_bg),
+ list: [
+ {
+ name: 'nickname',
+ type: 'text',
+ val: userInfo.nickname,
+ x: width * 0.22,
+ y: width * 0.06,
+ paintbrushProps: {
+ fillStyle: '#333',
+ font: {
+ fontSize: 16,
+ fontFamily: 'sans-serif',
+ },
+ },
+ },
+ {
+ name: 'avatar',
+ type: 'image',
+ val: sheep.$url.cdn(userInfo.avatar),
+ x: width * 0.04,
+ y: width * 0.04,
+ width: width * 0.14,
+ height: width * 0.14,
+ d: width * 0.14,
+ },
+ {
+ name: 'goodsImage',
+ type: 'image',
+ val: poster.shareInfo.poster.image,
+ x: width * 0.03,
+ y: width * 0.21,
+ width: width * 0.94,
+ height: width * 0.94,
+ r: 10,
+ },
+ {
+ name: 'goodsTitle',
+ type: 'text',
+ val: poster.shareInfo.poster.title,
+ x: width * 0.04,
+ y: width * 1.18,
+ maxWidth: width * 0.92,
+ lineHeight: 5,
+ paintbrushProps: {
+ fillStyle: '#333',
+ font: {
+ fontSize: 14,
+ },
+ },
+ },
+ {
+ name: 'goodsPrice',
+ type: 'text',
+ val: '¥' + poster.shareInfo.poster.price,
+ x: width * 0.04,
+ y: width * 1.3,
+ paintbrushProps: {
+ fillStyle: '#ff0000',
+ font: {
+ fontSize: 20,
+ fontFamily: 'OPPOSANS',
+ },
+ },
+ },
+ {
+ name: 'grouponNum',
+ type: 'text',
+ val: '2人团',
+ x: width * 0.3,
+ y: width * 1.32,
+ paintbrushProps: {
+ fillStyle: '#ff0000',
+ font: {
+ fontSize: 10,
+ fontFamily: 'OPPOSANS',
+ },
+ },
+ },
+ // #ifndef MP-WEIXIN
+ {
+ name: 'qrcode',
+ type: 'qrcode',
+ val: poster.shareInfo.link,
+ x: width * 0.75,
+ y: width * 1.3,
+ size: width * 0.2,
+ },
+ // #endif
+ // #ifdef MP-WEIXIN
+ {
+ name: 'wxacode',
+ type: 'image',
+ val: sheep.$api.third.wechat.getWxacode(poster.shareInfo.path),
+ x: width * 0.75,
+ y: width * 1.3,
+ width: width * 0.2,
+ height: width * 0.2,
+ },
+ // #endif
+ ],
+ };
+};
+
+export default groupon;
diff --git a/sheep/components/s-share-modal/canvas-poster/poster/index.js b/sheep/components/s-share-modal/canvas-poster/poster/index.js
new file mode 100644
index 00000000..500f8cbf
--- /dev/null
+++ b/sheep/components/s-share-modal/canvas-poster/poster/index.js
@@ -0,0 +1,14 @@
+import user from './user';
+import goods from './goods';
+import groupon from './groupon';
+
+export function getPosterData(options) {
+ switch (options.shareInfo.poster.type) {
+ case 'user':
+ return user(options);
+ case 'goods':
+ return goods(options);
+ case 'groupon':
+ return groupon(options);
+ }
+}
diff --git a/sheep/components/s-share-modal/canvas-poster/poster/user.js b/sheep/components/s-share-modal/canvas-poster/poster/user.js
new file mode 100644
index 00000000..ed134806
--- /dev/null
+++ b/sheep/components/s-share-modal/canvas-poster/poster/user.js
@@ -0,0 +1,60 @@
+import sheep from '@/sheep';
+
+const user = (poster) => {
+ const width = poster.width;
+ const userInfo = sheep.$store('user').userInfo;
+
+ return {
+ background: sheep.$url.cdn(sheep.$store('app').platform.share.posterInfo.user_bg),
+ list: [
+ {
+ name: 'nickname',
+ type: 'text',
+ val: userInfo.nickname,
+ x: width / 2,
+ y: width * 0.4,
+ paintbrushProps: {
+ textAlign: 'center',
+ fillStyle: '#333',
+ font: {
+ fontSize: 14,
+ fontFamily: 'sans-serif',
+ },
+ },
+ },
+ {
+ name: 'avatar',
+ type: 'image',
+ val: sheep.$url.cdn(userInfo.avatar),
+ x: width * 0.4,
+ y: width * 0.16,
+ width: width * 0.2,
+ height: width * 0.2,
+ d: width * 0.2,
+ },
+ // #ifndef MP-WEIXIN
+ {
+ name: 'qrcode',
+ type: 'qrcode',
+ val: poster.shareInfo.link,
+ x: width * 0.35,
+ y: width * 0.84,
+ size: width * 0.3,
+ },
+ // #endif
+ // #ifdef MP-WEIXIN
+ {
+ name: 'wxacode',
+ type: 'image',
+ val: sheep.$api.third.wechat.getWxacode(poster.shareInfo.path),
+ x: width * 0.35,
+ y: width * 0.84,
+ width: width * 0.3,
+ height: width * 0.3,
+ },
+ // #endif
+ ],
+ };
+};
+
+export default user;
diff --git a/sheep/components/s-share-modal/canvas-poster/useCanvas.js b/sheep/components/s-share-modal/canvas-poster/useCanvas.js
new file mode 100644
index 00000000..cd779472
--- /dev/null
+++ b/sheep/components/s-share-modal/canvas-poster/useCanvas.js
@@ -0,0 +1,89 @@
+/**
+ * Shopro + qs-canvas 绘制海报
+ * @version 1.0.0
+ * @author lidongtony
+ * @param {Object} options - 海报参数
+ * @param {Object} vm - 自定义组件实例
+ */
+
+import sheep from '@/sheep';
+import QSCanvas from 'qs-canvas';
+import { getPosterData } from './poster';
+
+export default async function useCanvas(options, vm) {
+ const width = options.width;
+ const qsc = new QSCanvas(
+ {
+ canvasId: options.canvasId,
+ width: options.width,
+ height: options.height,
+ setCanvasWH: (canvas) => {
+ options.height = canvas.height;
+ },
+ },
+ vm,
+ );
+
+ let drawer = getPosterData(options);
+
+ // 绘制背景图
+ const background = await qsc.drawImg({
+ type: 'image',
+ val: drawer.background,
+ x: 0,
+ y: 0,
+ width,
+ mode: 'widthFix',
+ zIndex: 0,
+ });
+ await qsc.updateCanvasWH({
+ width: background.width,
+ height: background.bottom,
+ });
+
+ let list = drawer.list;
+
+ for (let i = 0; i < list.length; i++) {
+ let item = list[i];
+ // 绘制文字
+ if (item.type === 'text') {
+ await qsc.drawText(item);
+ }
+ // 绘制图片
+ if (item.type === 'image') {
+ if (item.d) {
+ qsc.setCircle({
+ x: item.x,
+ y: item.y,
+ d: item.d,
+ clip: true,
+ });
+ }
+
+ if (item.r) {
+ qsc.setRect({
+ x: item.x,
+ y: item.y,
+ height: item.height,
+ width: item.width,
+ r: item.r,
+ clip: true,
+ });
+ }
+ await qsc.drawImg(item);
+ qsc.restore();
+ }
+
+ // 绘制二维码
+ if (item.type === 'qrcode') {
+ await qsc.drawQrCode(item);
+ }
+ }
+
+ await qsc.draw();
+ // 延迟执行, 防止不稳定
+ setTimeout(async () => {
+ options.src = await qsc.toImage();
+ }, 100);
+ return options;
+}
diff --git a/sheep/components/s-share-modal/s-share-modal.vue b/sheep/components/s-share-modal/s-share-modal.vue
new file mode 100644
index 00000000..83a4a27d
--- /dev/null
+++ b/sheep/components/s-share-modal/s-share-modal.vue
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-statusbar/s-statusbar.vue b/sheep/components/s-statusbar/s-statusbar.vue
new file mode 100644
index 00000000..97287206
--- /dev/null
+++ b/sheep/components/s-statusbar/s-statusbar.vue
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/sheep/components/s-tabbar/s-tabbar.vue b/sheep/components/s-tabbar/s-tabbar.vue
new file mode 100644
index 00000000..279019cb
--- /dev/null
+++ b/sheep/components/s-tabbar/s-tabbar.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-title-block/s-title-block.vue b/sheep/components/s-title-block/s-title-block.vue
new file mode 100644
index 00000000..3fc1aadf
--- /dev/null
+++ b/sheep/components/s-title-block/s-title-block.vue
@@ -0,0 +1,114 @@
+
+
+
+
+ {{
+ data.title.text
+ }}
+
+ {{ data.subtitle.text }}
+
+
+
+ 查看更多
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-uploader/choose-and-upload-file.js b/sheep/components/s-uploader/choose-and-upload-file.js
new file mode 100644
index 00000000..16db0b4a
--- /dev/null
+++ b/sheep/components/s-uploader/choose-and-upload-file.js
@@ -0,0 +1,213 @@
+'use strict';
+import sheep from '@/sheep';
+
+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;
+ })
+ .then(async (files) => {
+ for (let file of files.tempFiles) {
+ let { path } = await sheep.$api.app.upload(file.path, 'ugc');
+ file.url = path;
+ }
+ return files;
+ });
+}
+
+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/sheep/components/s-uploader/s-uploader.vue b/sheep/components/s-uploader/s-uploader.vue
new file mode 100644
index 00000000..cd3b4f6d
--- /dev/null
+++ b/sheep/components/s-uploader/s-uploader.vue
@@ -0,0 +1,674 @@
+
+
+
+
+ {{ subtitle }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-uploader/upload-file.vue b/sheep/components/s-uploader/upload-file.vue
new file mode 100644
index 00000000..233d281f
--- /dev/null
+++ b/sheep/components/s-uploader/upload-file.vue
@@ -0,0 +1,335 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
+
+
+
+ 点击重试
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-uploader/upload-image.vue b/sheep/components/s-uploader/upload-image.vue
new file mode 100644
index 00000000..f5f4b31d
--- /dev/null
+++ b/sheep/components/s-uploader/upload-image.vue
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-uploader/utils.js b/sheep/components/s-uploader/utils.js
new file mode 100644
index 00000000..c1e8073c
--- /dev/null
+++ b/sheep/components/s-uploader/utils.js
@@ -0,0 +1,110 @@
+/**
+ * 获取文件名和后缀
+ * @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/sheep/components/s-user-card/s-user-card.vue b/sheep/components/s-user-card/s-user-card.vue
new file mode 100644
index 00000000..2a19e1c8
--- /dev/null
+++ b/sheep/components/s-user-card/s-user-card.vue
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+
+
+ {{ userInfo?.nickname || nickname }}
+
+
+
+
+
+
+
+
+
+
+
+ 点击绑定手机号确保账户安全
+
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-video-block/s-video-block.vue b/sheep/components/s-video-block/s-video-block.vue
new file mode 100644
index 00000000..020c284b
--- /dev/null
+++ b/sheep/components/s-video-block/s-video-block.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
diff --git a/sheep/components/s-wallet-card/s-wallet-card.vue b/sheep/components/s-wallet-card/s-wallet-card.vue
new file mode 100644
index 00000000..a470f272
--- /dev/null
+++ b/sheep/components/s-wallet-card/s-wallet-card.vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
diff --git a/sheep/config/index.js b/sheep/config/index.js
new file mode 100644
index 00000000..6f737f6a
--- /dev/null
+++ b/sheep/config/index.js
@@ -0,0 +1,18 @@
+// 开发环境配置
+export let baseUrl;
+
+if (process.env.NODE_ENV === 'development') {
+ baseUrl = import.meta.env.SHOPRO_DEV_BASE_URL;
+} else {
+ baseUrl = import.meta.env.SHOPRO_BASE_URL;
+}
+console.log(`[Shopro v1.0.8] https://www.sheepjs.com/`);
+export const apiPath = import.meta.env.SHOPRO_API_PATH;
+
+export const staticUrl = import.meta.env.SHOPRO_STATIC_URL;
+
+export default {
+ baseUrl,
+ apiPath,
+ staticUrl,
+};
diff --git a/sheep/config/zIndex.js b/sheep/config/zIndex.js
new file mode 100644
index 00000000..6652d484
--- /dev/null
+++ b/sheep/config/zIndex.js
@@ -0,0 +1,20 @@
+// uniapp在H5中各API的z-index值如下:
+/**
+ * actionsheet: 999
+ * modal: 999
+ * navigate: 998
+ * tabbar: 998
+ * toast: 999
+ */
+
+export default {
+ toast: 10090,
+ noNetwork: 10080,
+ popup: 10075, // popup包含popup,actionsheet,keyboard,picker的值
+ mask: 10070,
+ navbar: 980,
+ topTips: 975,
+ sticky: 970,
+ indexListSticky: 965,
+ popover: 960,
+};
diff --git a/sheep/helper/digit.js b/sheep/helper/digit.js
new file mode 100644
index 00000000..be50b32f
--- /dev/null
+++ b/sheep/helper/digit.js
@@ -0,0 +1,168 @@
+let _boundaryCheckingState = true; // 是否进行越界检查的全局开关
+
+/**
+ * 把错误的数据转正
+ * @private
+ * @example strip(0.09999999999999998)=0.1
+ */
+function strip(num, precision = 15) {
+ return +parseFloat(Number(num).toPrecision(precision));
+}
+
+/**
+ * Return digits length of a number
+ * @private
+ * @param {*number} num Input number
+ */
+function digitLength(num) {
+ // Get digit length of e
+ const eSplit = num.toString().split(/[eE]/);
+ const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0);
+ return len > 0 ? len : 0;
+}
+
+/**
+ * 把小数转成整数,如果是小数则放大成整数
+ * @private
+ * @param {*number} num 输入数
+ */
+function float2Fixed(num) {
+ if (num.toString().indexOf('e') === -1) {
+ return Number(num.toString().replace('.', ''));
+ }
+ const dLen = digitLength(num);
+ return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num);
+}
+
+/**
+ * 检测数字是否越界,如果越界给出提示
+ * @private
+ * @param {*number} num 输入数
+ */
+function checkBoundary(num) {
+ if (_boundaryCheckingState) {
+ if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
+ console.warn(`${num} 超出了精度限制,结果可能不正确`);
+ }
+ }
+}
+
+/**
+ * 把递归操作扁平迭代化
+ * @param {number[]} arr 要操作的数字数组
+ * @param {function} operation 迭代操作
+ * @private
+ */
+function iteratorOperation(arr, operation) {
+ const [num1, num2, ...others] = arr;
+ let res = operation(num1, num2);
+
+ others.forEach((num) => {
+ res = operation(res, num);
+ });
+
+ return res;
+}
+
+/**
+ * 高精度乘法
+ * @export
+ */
+export function times(...nums) {
+ if (nums.length > 2) {
+ return iteratorOperation(nums, times);
+ }
+
+ const [num1, num2] = nums;
+ const num1Changed = float2Fixed(num1);
+ const num2Changed = float2Fixed(num2);
+ const baseNum = digitLength(num1) + digitLength(num2);
+ const leftValue = num1Changed * num2Changed;
+
+ checkBoundary(leftValue);
+
+ return leftValue / Math.pow(10, baseNum);
+}
+
+/**
+ * 高精度加法
+ * @export
+ */
+export function plus(...nums) {
+ if (nums.length > 2) {
+ return iteratorOperation(nums, plus);
+ }
+
+ const [num1, num2] = nums;
+ // 取最大的小数位
+ const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
+ // 把小数都转为整数然后再计算
+ return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;
+}
+
+/**
+ * 高精度减法
+ * @export
+ */
+export function minus(...nums) {
+ if (nums.length > 2) {
+ return iteratorOperation(nums, minus);
+ }
+
+ const [num1, num2] = nums;
+ const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
+ return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;
+}
+
+/**
+ * 高精度除法
+ * @export
+ */
+export function divide(...nums) {
+ if (nums.length > 2) {
+ return iteratorOperation(nums, divide);
+ }
+
+ const [num1, num2] = nums;
+ const num1Changed = float2Fixed(num1);
+ const num2Changed = float2Fixed(num2);
+ checkBoundary(num1Changed);
+ checkBoundary(num2Changed);
+ // 重要,这里必须用strip进行修正
+ return times(
+ num1Changed / num2Changed,
+ strip(Math.pow(10, digitLength(num2) - digitLength(num1))),
+ );
+}
+
+/**
+ * 四舍五入
+ * @export
+ */
+export function round(num, ratio) {
+ const base = Math.pow(10, ratio);
+ let result = divide(Math.round(Math.abs(times(num, base))), base);
+ if (num < 0 && result !== 0) {
+ result = times(result, -1);
+ }
+ // 位数不足则补0
+ return result;
+}
+
+/**
+ * 是否进行边界检查,默认开启
+ * @param flag 标记开关,true 为开启,false 为关闭,默认为 true
+ * @export
+ */
+export function enableBoundaryChecking(flag = true) {
+ _boundaryCheckingState = flag;
+}
+
+export default {
+ times,
+ plus,
+ minus,
+ divide,
+ round,
+ enableBoundaryChecking,
+};
diff --git a/sheep/helper/index.js b/sheep/helper/index.js
new file mode 100644
index 00000000..7a9504d2
--- /dev/null
+++ b/sheep/helper/index.js
@@ -0,0 +1,708 @@
+import test from './test.js';
+import { round } from './digit.js';
+/**
+ * @description 如果value小于min,取min;如果value大于max,取max
+ * @param {number} min
+ * @param {number} max
+ * @param {number} value
+ */
+function range(min = 0, max = 0, value = 0) {
+ return Math.max(min, Math.min(max, Number(value)));
+}
+
+/**
+ * @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换
+ * @param {number|string} value 用户传递值的px值
+ * @param {boolean} unit
+ * @returns {number|string}
+ */
+export function getPx(value, unit = false) {
+ if (test.number(value)) {
+ return unit ? `${value}px` : Number(value);
+ }
+ // 如果带有rpx,先取出其数值部分,再转为px值
+ if (/(rpx|upx)$/.test(value)) {
+ return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)));
+ }
+ return unit ? `${parseInt(value)}px` : parseInt(value);
+}
+
+/**
+ * @description 进行延时,以达到可以简写代码的目的
+ * @param {number} value 堵塞时间 单位ms 毫秒
+ * @returns {Promise} 返回promise
+ */
+export function sleep(value = 30) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve();
+ }, value);
+ });
+}
+/**
+ * @description 运行期判断平台
+ * @returns {string} 返回所在平台(小写)
+ * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
+ */
+export function os() {
+ return uni.getSystemInfoSync().platform.toLowerCase();
+}
+/**
+ * @description 获取系统信息同步接口
+ * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
+ */
+export function sys() {
+ return uni.getSystemInfoSync();
+}
+
+/**
+ * @description 取一个区间数
+ * @param {Number} min 最小值
+ * @param {Number} max 最大值
+ */
+function random(min, max) {
+ if (min >= 0 && max > 0 && max >= min) {
+ const gab = max - min + 1;
+ return Math.floor(Math.random() * gab + min);
+ }
+ return 0;
+}
+
+/**
+ * @param {Number} len uuid的长度
+ * @param {Boolean} firstU 将返回的首字母置为"u"
+ * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
+ */
+export function guid(len = 32, firstU = true, radix = null) {
+ const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
+ const uuid = [];
+ radix = radix || chars.length;
+
+ if (len) {
+ // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
+ for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
+ } else {
+ let r;
+ // rfc4122标准要求返回的uuid中,某些位为固定的字符
+ uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
+ uuid[14] = '4';
+
+ for (let i = 0; i < 36; i++) {
+ if (!uuid[i]) {
+ r = 0 | (Math.random() * 16);
+ uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
+ }
+ }
+ }
+ // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
+ if (firstU) {
+ uuid.shift();
+ return `u${uuid.join('')}`;
+ }
+ return uuid.join('');
+}
+
+/**
+* @description 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法
+ this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
+ 这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name
+ 值(默认为undefined),就是查找最顶层的$parent
+* @param {string|undefined} name 父组件的参数名
+*/
+export function $parent(name = undefined) {
+ let parent = this.$parent;
+ // 通过while历遍,这里主要是为了H5需要多层解析的问题
+ while (parent) {
+ // 父组件
+ if (parent.$options && parent.$options.name !== name) {
+ // 如果组件的name不相等,继续上一级寻找
+ parent = parent.$parent;
+ } else {
+ return parent;
+ }
+ }
+ return false;
+}
+
+/**
+ * @description 样式转换
+ * 对象转字符串,或者字符串转对象
+ * @param {object | string} customStyle 需要转换的目标
+ * @param {String} target 转换的目的,object-转为对象,string-转为字符串
+ * @returns {object|string}
+ */
+export function addStyle(customStyle, target = 'object') {
+ // 字符串转字符串,对象转对象情形,直接返回
+ if (
+ test.empty(customStyle) ||
+ (typeof customStyle === 'object' && target === 'object') ||
+ (target === 'string' && typeof customStyle === 'string')
+ ) {
+ return customStyle;
+ }
+ // 字符串转对象
+ if (target === 'object') {
+ // 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
+ customStyle = trim(customStyle);
+ // 根据";"将字符串转为数组形式
+ const styleArray = customStyle.split(';');
+ const style = {};
+ // 历遍数组,拼接成对象
+ for (let i = 0; i < styleArray.length; i++) {
+ // 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
+ if (styleArray[i]) {
+ const item = styleArray[i].split(':');
+ style[trim(item[0])] = trim(item[1]);
+ }
+ }
+ return style;
+ }
+ // 这里为对象转字符串形式
+ let string = '';
+ for (const i in customStyle) {
+ // 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
+ const key = i.replace(/([A-Z])/g, '-$1').toLowerCase();
+ string += `${key}:${customStyle[i]};`;
+ }
+ // 去除两端空格
+ return trim(string);
+}
+
+/**
+ * @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾
+ * @param {string|number} value 需要添加单位的值
+ * @param {string} unit 添加的单位名 比如px
+ */
+export function addUnit(value = 'auto', unit = 'px') {
+ value = String(value);
+ return test.number(value) ? `${value}${unit}` : value;
+}
+
+/**
+ * @description 深度克隆
+ * @param {object} obj 需要深度克隆的对象
+ * @returns {*} 克隆后的对象或者原值(不是对象)
+ */
+function deepClone(obj) {
+ // 对常见的“非”值,直接返回原来值
+ if ([null, undefined, NaN, false].includes(obj)) return obj;
+ if (typeof obj !== 'object' && typeof obj !== 'function') {
+ // 原始类型直接返回
+ return obj;
+ }
+ const o = test.array(obj) ? [] : {};
+ for (const i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i];
+ }
+ }
+ return o;
+}
+
+/**
+ * @description JS对象深度合并
+ * @param {object} target 需要拷贝的对象
+ * @param {object} source 拷贝的来源对象
+ * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
+ */
+export function deepMerge(target = {}, source = {}) {
+ target = deepClone(target);
+ if (typeof target !== 'object' || typeof source !== 'object') return false;
+ for (const prop in source) {
+ if (!source.hasOwnProperty(prop)) continue;
+ if (prop in target) {
+ if (typeof target[prop] !== 'object') {
+ target[prop] = source[prop];
+ } else if (typeof source[prop] !== 'object') {
+ target[prop] = source[prop];
+ } else if (target[prop].concat && source[prop].concat) {
+ target[prop] = target[prop].concat(source[prop]);
+ } else {
+ target[prop] = deepMerge(target[prop], source[prop]);
+ }
+ } else {
+ target[prop] = source[prop];
+ }
+ }
+ return target;
+}
+
+/**
+ * @description error提示
+ * @param {*} err 错误内容
+ */
+function error(err) {
+ // 开发环境才提示,生产环境不会提示
+ if (process.env.NODE_ENV === 'development') {
+ console.error(`SheepJS:${err}`);
+ }
+}
+
+/**
+ * @description 打乱数组
+ * @param {array} array 需要打乱的数组
+ * @returns {array} 打乱后的数组
+ */
+function randomArray(array = []) {
+ // 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
+ return array.sort(() => Math.random() - 0.5);
+}
+
+// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
+// 所以这里做一个兼容polyfill的兼容处理
+if (!String.prototype.padStart) {
+ // 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
+ String.prototype.padStart = function (maxLength, fillString = ' ') {
+ if (Object.prototype.toString.call(fillString) !== '[object String]') {
+ throw new TypeError('fillString must be String');
+ }
+ const str = this;
+ // 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
+ if (str.length >= maxLength) return String(str);
+
+ const fillLength = maxLength - str.length;
+ let times = Math.ceil(fillLength / fillString.length);
+ while ((times >>= 1)) {
+ fillString += fillString;
+ if (times === 1) {
+ fillString += fillString;
+ }
+ }
+ return fillString.slice(0, fillLength) + str;
+ };
+}
+
+/**
+ * @description 格式化时间
+ * @param {String|Number} dateTime 需要格式化的时间戳
+ * @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd
+ * @returns {string} 返回格式化后的字符串
+ */
+function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {
+ let date;
+ // 若传入时间为假值,则取当前时间
+ if (!dateTime) {
+ date = new Date();
+ }
+ // 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容)
+ else if (/^\d{10}$/.test(dateTime?.toString().trim())) {
+ date = new Date(dateTime * 1000);
+ }
+ // 若用户传入字符串格式时间戳,new Date无法解析,需做兼容
+ else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) {
+ date = new Date(Number(dateTime));
+ }
+ // 其他都认为符合 RFC 2822 规范
+ else {
+ // 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间
+ date = new Date(typeof dateTime === 'string' ? dateTime.replace(/-/g, '/') : dateTime);
+ }
+
+ const timeSource = {
+ y: date.getFullYear().toString(), // 年
+ m: (date.getMonth() + 1).toString().padStart(2, '0'), // 月
+ d: date.getDate().toString().padStart(2, '0'), // 日
+ h: date.getHours().toString().padStart(2, '0'), // 时
+ M: date.getMinutes().toString().padStart(2, '0'), // 分
+ s: date.getSeconds().toString().padStart(2, '0'), // 秒
+ // 有其他格式化字符需求可以继续添加,必须转化成字符串
+ };
+
+ for (const key in timeSource) {
+ const [ret] = new RegExp(`${key}+`).exec(formatStr) || [];
+ if (ret) {
+ // 年可能只需展示两位
+ const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0;
+ formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex));
+ }
+ }
+
+ return formatStr;
+}
+
+/**
+ * @description 时间戳转为多久之前
+ * @param {String|Number} timestamp 时间戳
+ * @param {String|Boolean} format
+ * 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
+ * 如果为布尔值false,无论什么时间,都返回多久以前的格式
+ * @returns {string} 转化后的内容
+ */
+function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
+ if (timestamp == null) timestamp = Number(new Date());
+ timestamp = parseInt(timestamp);
+ // 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
+ if (timestamp.toString().length == 10) timestamp *= 1000;
+ let timer = new Date().getTime() - timestamp;
+ timer = parseInt(timer / 1000);
+ // 如果小于5分钟,则返回"刚刚",其他以此类推
+ let tips = '';
+ switch (true) {
+ case timer < 300:
+ tips = '刚刚';
+ break;
+ case timer >= 300 && timer < 3600:
+ tips = `${parseInt(timer / 60)}分钟前`;
+ break;
+ case timer >= 3600 && timer < 86400:
+ tips = `${parseInt(timer / 3600)}小时前`;
+ break;
+ case timer >= 86400 && timer < 2592000:
+ tips = `${parseInt(timer / 86400)}天前`;
+ break;
+ default:
+ // 如果format为false,则无论什么时间戳,都显示xx之前
+ if (format === false) {
+ if (timer >= 2592000 && timer < 365 * 86400) {
+ tips = `${parseInt(timer / (86400 * 30))}个月前`;
+ } else {
+ tips = `${parseInt(timer / (86400 * 365))}年前`;
+ }
+ } else {
+ tips = timeFormat(timestamp, format);
+ }
+ }
+ return tips;
+}
+
+/**
+ * @description 去除空格
+ * @param String str 需要去除空格的字符串
+ * @param String pos both(左右)|left|right|all 默认both
+ */
+function trim(str, pos = 'both') {
+ str = String(str);
+ if (pos == 'both') {
+ return str.replace(/^\s+|\s+$/g, '');
+ }
+ if (pos == 'left') {
+ return str.replace(/^\s*/, '');
+ }
+ if (pos == 'right') {
+ return str.replace(/(\s*$)/g, '');
+ }
+ if (pos == 'all') {
+ return str.replace(/\s+/g, '');
+ }
+ return str;
+}
+
+/**
+ * @description 对象转url参数
+ * @param {object} data,对象
+ * @param {Boolean} isPrefix,是否自动加上"?"
+ * @param {string} arrayFormat 规则 indices|brackets|repeat|comma
+ */
+function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
+ const prefix = isPrefix ? '?' : '';
+ const _result = [];
+ if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1)
+ arrayFormat = 'brackets';
+ for (const key in data) {
+ const value = data[key];
+ // 去掉为空的参数
+ if (['', undefined, null].indexOf(value) >= 0) {
+ continue;
+ }
+ // 如果值为数组,另行处理
+ if (value.constructor === Array) {
+ // e.g. {ids: [1, 2, 3]}
+ switch (arrayFormat) {
+ case 'indices':
+ // 结果: ids[0]=1&ids[1]=2&ids[2]=3
+ for (let i = 0; i < value.length; i++) {
+ _result.push(`${key}[${i}]=${value[i]}`);
+ }
+ break;
+ case 'brackets':
+ // 结果: ids[]=1&ids[]=2&ids[]=3
+ value.forEach((_value) => {
+ _result.push(`${key}[]=${_value}`);
+ });
+ break;
+ case 'repeat':
+ // 结果: ids=1&ids=2&ids=3
+ value.forEach((_value) => {
+ _result.push(`${key}=${_value}`);
+ });
+ break;
+ case 'comma':
+ // 结果: ids=1,2,3
+ let commaStr = '';
+ value.forEach((_value) => {
+ commaStr += (commaStr ? ',' : '') + _value;
+ });
+ _result.push(`${key}=${commaStr}`);
+ break;
+ default:
+ value.forEach((_value) => {
+ _result.push(`${key}[]=${_value}`);
+ });
+ }
+ } else {
+ _result.push(`${key}=${value}`);
+ }
+ }
+ return _result.length ? prefix + _result.join('&') : '';
+}
+
+/**
+ * 显示消息提示框
+ * @param {String} title 提示的内容,长度与 icon 取值有关。
+ * @param {Number} duration 提示的延迟时间,单位毫秒,默认:2000
+ */
+function toast(title, duration = 2000) {
+ uni.showToast({
+ title: String(title),
+ icon: 'none',
+ duration,
+ });
+}
+
+/**
+ * @description 根据主题type值,获取对应的图标
+ * @param {String} type 主题名称,primary|info|error|warning|success
+ * @param {boolean} fill 是否使用fill填充实体的图标
+ */
+function type2icon(type = 'success', fill = false) {
+ // 如果非预置值,默认为success
+ if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success';
+ let iconName = '';
+ // 目前(2019-12-12),info和primary使用同一个图标
+ switch (type) {
+ case 'primary':
+ iconName = 'info-circle';
+ break;
+ case 'info':
+ iconName = 'info-circle';
+ break;
+ case 'error':
+ iconName = 'close-circle';
+ break;
+ case 'warning':
+ iconName = 'error-circle';
+ break;
+ case 'success':
+ iconName = 'checkmark-circle';
+ break;
+ default:
+ iconName = 'checkmark-circle';
+ }
+ // 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
+ if (fill) iconName += '-fill';
+ return iconName;
+}
+
+/**
+ * @description 数字格式化
+ * @param {number|string} number 要格式化的数字
+ * @param {number} decimals 保留几位小数
+ * @param {string} decimalPoint 小数点符号
+ * @param {string} thousandsSeparator 千分位符号
+ * @returns {string} 格式化后的数字
+ */
+function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
+ number = `${number}`.replace(/[^0-9+-Ee.]/g, '');
+ const n = !isFinite(+number) ? 0 : +number;
+ const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
+ const sep = typeof thousandsSeparator === 'undefined' ? ',' : thousandsSeparator;
+ const dec = typeof decimalPoint === 'undefined' ? '.' : decimalPoint;
+ let s = '';
+
+ s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.');
+ const re = /(-?\d+)(\d{3})/;
+ while (re.test(s[0])) {
+ s[0] = s[0].replace(re, `$1${sep}$2`);
+ }
+
+ if ((s[1] || '').length < prec) {
+ s[1] = s[1] || '';
+ s[1] += new Array(prec - s[1].length + 1).join('0');
+ }
+ return s.join(dec);
+}
+
+/**
+ * @description 获取duration值
+ * 如果带有ms或者s直接返回,如果大于一定值,认为是ms单位,小于一定值,认为是s单位
+ * 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画
+ * @param {String|number} value 比如: "1s"|"100ms"|1|100
+ * @param {boolean} unit 提示: 如果是false 默认返回number
+ * @return {string|number}
+ */
+function getDuration(value, unit = true) {
+ const valueNum = parseInt(value);
+ if (unit) {
+ if (/s$/.test(value)) return value;
+ return value > 30 ? `${value}ms` : `${value}s`;
+ }
+ if (/ms$/.test(value)) return valueNum;
+ if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000;
+ return valueNum;
+}
+
+/**
+ * @description 日期的月或日补零操作
+ * @param {String} value 需要补零的值
+ */
+function padZero(value) {
+ return `00${value}`.slice(-2);
+}
+
+/**
+ * @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
+ * @param {object} obj 对象
+ * @param {string} key 需要获取的属性字段
+ * @returns {*}
+ */
+function getProperty(obj, key) {
+ if (!obj) {
+ return;
+ }
+ if (typeof key !== 'string' || key === '') {
+ return '';
+ }
+ if (key.indexOf('.') !== -1) {
+ const keys = key.split('.');
+ let firstObj = obj[keys[0]] || {};
+
+ for (let i = 1; i < keys.length; i++) {
+ if (firstObj) {
+ firstObj = firstObj[keys[i]];
+ }
+ }
+ return firstObj;
+ }
+ return obj[key];
+}
+
+/**
+ * @description 设置对象的属性值,如果'a.b.c'的形式进行设置
+ * @param {object} obj 对象
+ * @param {string} key 需要设置的属性
+ * @param {string} value 设置的值
+ */
+function setProperty(obj, key, value) {
+ if (!obj) {
+ return;
+ }
+ // 递归赋值
+ const inFn = function (_obj, keys, v) {
+ // 最后一个属性key
+ if (keys.length === 1) {
+ _obj[keys[0]] = v;
+ return;
+ }
+ // 0~length-1个key
+ while (keys.length > 1) {
+ const k = keys[0];
+ if (!_obj[k] || typeof _obj[k] !== 'object') {
+ _obj[k] = {};
+ }
+ const key = keys.shift();
+ // 自调用判断是否存在属性,不存在则自动创建对象
+ inFn(_obj[k], keys, v);
+ }
+ };
+
+ if (typeof key !== 'string' || key === '') {
+ } else if (key.indexOf('.') !== -1) {
+ // 支持多层级赋值操作
+ const keys = key.split('.');
+ inFn(obj, keys, value);
+ } else {
+ obj[key] = value;
+ }
+}
+
+/**
+ * @description 获取当前页面路径
+ */
+function page() {
+ const pages = getCurrentPages();
+ // 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组
+ return `/${pages[pages.length - 1]?.route ?? ''}`;
+}
+
+/**
+ * @description 获取当前路由栈实例数组
+ */
+function pages() {
+ const pages = getCurrentPages();
+ return pages;
+}
+
+/**
+ * 获取H5-真实根地址 兼容hash+history模式
+ */
+export function getRootUrl() {
+ let url = '';
+ // #ifdef H5
+ url = location.origin + location.pathname;
+
+ if (location.hash !== '') {
+ url += '#/';
+ }
+ // #endif
+ return url;
+}
+
+/**
+ * copyText 多端复制文本
+ */
+export function copyText(text) {
+ // #ifndef H5
+ uni.setClipboardData({
+ data: text,
+ success: function () {
+ toast('复制成功!');
+ },
+ fail: function () {
+ toast('复制失败!');
+ },
+ });
+ // #endif
+ // #ifdef H5
+ var createInput = document.createElement('textarea');
+ createInput.value = text;
+ document.body.appendChild(createInput);
+ createInput.select();
+ document.execCommand('Copy');
+ createInput.className = 'createInput';
+ createInput.style.display = 'none';
+ toast('复制成功');
+ // #endif
+}
+
+export default {
+ range,
+ getPx,
+ sleep,
+ os,
+ sys,
+ random,
+ guid,
+ $parent,
+ addStyle,
+ addUnit,
+ deepClone,
+ deepMerge,
+ error,
+ randomArray,
+ timeFormat,
+ timeFrom,
+ trim,
+ queryParams,
+ toast,
+ type2icon,
+ priceFormat,
+ getDuration,
+ padZero,
+ getProperty,
+ setProperty,
+ page,
+ pages,
+ test,
+ getRootUrl,
+ copyText,
+};
diff --git a/sheep/helper/test.js b/sheep/helper/test.js
new file mode 100644
index 00000000..ca550a14
--- /dev/null
+++ b/sheep/helper/test.js
@@ -0,0 +1,285 @@
+/**
+ * 验证电子邮箱格式
+ */
+function email(value) {
+ return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value);
+}
+
+/**
+ * 验证手机格式
+ */
+function mobile(value) {
+ return /^1[23456789]\d{9}$/.test(value);
+}
+
+/**
+ * 验证URL格式
+ */
+function url(value) {
+ return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/.test(
+ value,
+ );
+}
+
+/**
+ * 验证日期格式
+ */
+function date(value) {
+ if (!value) return false;
+ // 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳
+ if (number(value)) value = +value;
+ return !/Invalid|NaN/.test(new Date(value).toString());
+}
+
+/**
+ * 验证ISO类型的日期格式
+ */
+function dateISO(value) {
+ return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value);
+}
+
+/**
+ * 验证十进制数字
+ */
+function number(value) {
+ return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value);
+}
+
+/**
+ * 验证字符串
+ */
+function string(value) {
+ return typeof value === 'string';
+}
+
+/**
+ * 验证整数
+ */
+function digits(value) {
+ return /^\d+$/.test(value);
+}
+
+/**
+ * 验证身份证号码
+ */
+function idCard(value) {
+ return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value);
+}
+
+/**
+ * 是否车牌号
+ */
+function carNo(value) {
+ // 新能源车牌
+ const xreg =
+ /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
+ // 旧车牌
+ const creg =
+ /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
+ if (value.length === 7) {
+ return creg.test(value);
+ }
+ if (value.length === 8) {
+ return xreg.test(value);
+ }
+ return false;
+}
+
+/**
+ * 金额,只允许2位小数
+ */
+function amount(value) {
+ // 金额,只允许保留两位小数
+ return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value);
+}
+
+/**
+ * 中文
+ */
+function chinese(value) {
+ const reg = /^[\u4e00-\u9fa5]+$/gi;
+ return reg.test(value);
+}
+
+/**
+ * 只能输入字母
+ */
+function letter(value) {
+ return /^[a-zA-Z]*$/.test(value);
+}
+
+/**
+ * 只能是字母或者数字
+ */
+function enOrNum(value) {
+ // 英文或者数字
+ const reg = /^[0-9a-zA-Z]*$/g;
+ return reg.test(value);
+}
+
+/**
+ * 验证是否包含某个值
+ */
+function contains(value, param) {
+ return value.indexOf(param) >= 0;
+}
+
+/**
+ * 验证一个值范围[min, max]
+ */
+function range(value, param) {
+ return value >= param[0] && value <= param[1];
+}
+
+/**
+ * 验证一个长度范围[min, max]
+ */
+function rangeLength(value, param) {
+ return value.length >= param[0] && value.length <= param[1];
+}
+
+/**
+ * 是否固定电话
+ */
+function landline(value) {
+ const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/;
+ return reg.test(value);
+}
+
+/**
+ * 判断是否为空
+ */
+function empty(value) {
+ switch (typeof value) {
+ case 'undefined':
+ return true;
+ case 'string':
+ if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true;
+ break;
+ case 'boolean':
+ if (!value) return true;
+ break;
+ case 'number':
+ if (value === 0 || isNaN(value)) return true;
+ break;
+ case 'object':
+ if (value === null || value.length === 0) return true;
+ for (const i in value) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * 是否json字符串
+ */
+function jsonString(value) {
+ if (typeof value === 'string') {
+ try {
+ const obj = JSON.parse(value);
+ if (typeof obj === 'object' && obj) {
+ return true;
+ }
+ return false;
+ } catch (e) {
+ return false;
+ }
+ }
+ return false;
+}
+
+/**
+ * 是否数组
+ */
+function array(value) {
+ if (typeof Array.isArray === 'function') {
+ return Array.isArray(value);
+ }
+ return Object.prototype.toString.call(value) === '[object Array]';
+}
+
+/**
+ * 是否对象
+ */
+function object(value) {
+ return Object.prototype.toString.call(value) === '[object Object]';
+}
+
+/**
+ * 是否短信验证码
+ */
+function code(value, len = 6) {
+ return new RegExp(`^\\d{${len}}$`).test(value);
+}
+
+/**
+ * 是否函数方法
+ * @param {Object} value
+ */
+function func(value) {
+ return typeof value === 'function';
+}
+
+/**
+ * 是否promise对象
+ * @param {Object} value
+ */
+function promise(value) {
+ return object(value) && func(value.then) && func(value.catch);
+}
+
+/** 是否图片格式
+ * @param {Object} value
+ */
+function image(value) {
+ const newValue = value.split('?')[0];
+ const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i;
+ return IMAGE_REGEXP.test(newValue);
+}
+
+/**
+ * 是否视频格式
+ * @param {Object} value
+ */
+function video(value) {
+ const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i;
+ return VIDEO_REGEXP.test(value);
+}
+
+/**
+ * 是否为正则对象
+ * @param {Object}
+ * @return {Boolean}
+ */
+function regExp(o) {
+ return o && Object.prototype.toString.call(o) === '[object RegExp]';
+}
+
+export default {
+ email,
+ mobile,
+ url,
+ date,
+ dateISO,
+ number,
+ digits,
+ idCard,
+ carNo,
+ amount,
+ chinese,
+ letter,
+ enOrNum,
+ contains,
+ range,
+ rangeLength,
+ empty,
+ isEmpty: empty,
+ isNumber: number,
+ jsonString,
+ landline,
+ object,
+ array,
+ code,
+};
diff --git a/sheep/helper/throttle.js b/sheep/helper/throttle.js
new file mode 100644
index 00000000..c3181271
--- /dev/null
+++ b/sheep/helper/throttle.js
@@ -0,0 +1,31 @@
+let timer;
+let flag;
+/**
+ * 节流原理:在一定时间内,只能触发一次
+ *
+ * @param {Function} func 要执行的回调函数
+ * @param {Number} wait 延时的时间
+ * @param {Boolean} immediate 是否立即执行
+ * @return null
+ */
+function throttle(func, wait = 500, immediate = true) {
+ if (immediate) {
+ if (!flag) {
+ flag = true;
+ // 如果是立即执行,则在wait毫秒内开始时执行
+ typeof func === 'function' && func();
+ timer = setTimeout(() => {
+ flag = false;
+ }, wait);
+ } else {
+ }
+ } else if (!flag) {
+ flag = true;
+ // 如果是非立即执行,则在wait毫秒内的结束处执行
+ timer = setTimeout(() => {
+ flag = false;
+ typeof func === 'function' && func();
+ }, wait);
+ }
+}
+export default throttle;
diff --git a/sheep/helper/tools.js b/sheep/helper/tools.js
new file mode 100644
index 00000000..49efcecb
--- /dev/null
+++ b/sheep/helper/tools.js
@@ -0,0 +1,67 @@
+import router from '@/sheep/router';
+export default {
+ /**
+ * 打电话
+ * @param {String} phoneNumber - 数字字符串
+ */
+ callPhone(phoneNumber = '') {
+ let num = phoneNumber.toString();
+ uni.makePhoneCall({
+ phoneNumber: num,
+ fail(err) {
+ console.log('makePhoneCall出错', err);
+ },
+ });
+ },
+
+ /**
+ * 微信头像
+ * @param {String} url -图片地址
+ */
+ checkMPUrl(url) {
+ // #ifdef MP
+ if (
+ url.substring(0, 4) === 'http' &&
+ url.substring(0, 5) !== 'https' &&
+ url.substring(0, 12) !== 'http://store' &&
+ url.substring(0, 10) !== 'http://tmp' &&
+ url.substring(0, 10) !== 'http://usr'
+ ) {
+ url = 'https' + url.substring(4, url.length);
+ }
+ // #endif
+ return url;
+ },
+
+ /**
+ * getUuid 生成唯一id
+ */
+ getUuid(len = 32, firstU = true, radix = null) {
+ const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
+ const uuid = [];
+ radix = radix || chars.length;
+
+ if (len) {
+ // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
+ for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * radix)];
+ } else {
+ let r;
+ // rfc4122标准要求返回的uuid中,某些位为固定的字符
+ uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
+ uuid[14] = '4';
+
+ for (let i = 0; i < 36; i++) {
+ if (!uuid[i]) {
+ r = 0 | (Math.random() * 16);
+ uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
+ }
+ }
+ }
+ // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
+ if (firstU) {
+ uuid.shift();
+ return `u${uuid.join('')}`;
+ }
+ return uuid.join('');
+ },
+};
diff --git a/sheep/helper/utils.js b/sheep/helper/utils.js
new file mode 100644
index 00000000..bcb5d25e
--- /dev/null
+++ b/sheep/helper/utils.js
@@ -0,0 +1,168 @@
+export function isArray(value) {
+ if (typeof Array.isArray === 'function') {
+ return Array.isArray(value);
+ } else {
+ return Object.prototype.toString.call(value) === '[object Array]';
+ }
+}
+
+export function isObject(value) {
+ return Object.prototype.toString.call(value) === '[object Object]';
+}
+
+export function isNumber(value) {
+ return !isNaN(Number(value));
+}
+
+export function isFunction(value) {
+ return typeof value == 'function';
+}
+
+export function isString(value) {
+ return typeof value == 'string';
+}
+
+export function isEmpty(value) {
+ if (isArray(value)) {
+ return value.length === 0;
+ }
+
+ if (isObject(value)) {
+ return Object.keys(value).length === 0;
+ }
+
+ return value === '' || value === undefined || value === null;
+}
+
+export function isBoolean(value) {
+ return typeof value === 'boolean';
+}
+
+export function last(data) {
+ if (isArray(data) || isString(data)) {
+ return data[data.length - 1];
+ }
+}
+
+export function cloneDeep(obj) {
+ const d = isArray(obj) ? obj : {};
+
+ if (isObject(obj)) {
+ for (const key in obj) {
+ if (obj[key]) {
+ if (obj[key] && typeof obj[key] === 'object') {
+ d[key] = cloneDeep(obj[key]);
+ } else {
+ d[key] = obj[key];
+ }
+ }
+ }
+ }
+
+ return d;
+}
+
+export function clone(obj) {
+ return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
+}
+
+export function deepMerge(a, b) {
+ let k;
+ for (k in b) {
+ a[k] = a[k] && a[k].toString() === '[object Object]' ? deepMerge(a[k], b[k]) : (a[k] = b[k]);
+ }
+ return a;
+}
+
+export function contains(parent, node) {
+ while (node && (node = node.parentNode)) if (node === parent) return true;
+ return false;
+}
+
+export function orderBy(list, key) {
+ return list.sort((a, b) => a[key] - b[key]);
+}
+
+export function deepTree(list) {
+ const newList = [];
+ const map = {};
+
+ list.forEach((e) => (map[e.id] = e));
+
+ list.forEach((e) => {
+ const parent = map[e.parentId];
+
+ if (parent) {
+ (parent.children || (parent.children = [])).push(e);
+ } else {
+ newList.push(e);
+ }
+ });
+
+ const fn = (list) => {
+ list.map((e) => {
+ if (e.children instanceof Array) {
+ e.children = orderBy(e.children, 'orderNum');
+
+ fn(e.children);
+ }
+ });
+ };
+
+ fn(newList);
+
+ return orderBy(newList, 'orderNum');
+}
+
+export function revDeepTree(list = []) {
+ const d = [];
+ let id = 0;
+
+ const deep = (list, parentId) => {
+ list.forEach((e) => {
+ if (!e.id) {
+ e.id = id++;
+ }
+
+ e.parentId = parentId;
+
+ d.push(e);
+
+ if (e.children && isArray(e.children)) {
+ deep(e.children, e.id);
+ }
+ });
+ };
+
+ deep(list || [], null);
+
+ return d;
+}
+
+export function basename(path) {
+ let index = path.lastIndexOf('/');
+ index = index > -1 ? index : path.lastIndexOf('\\');
+ if (index < 0) {
+ return path;
+ }
+ return path.substring(index + 1);
+}
+
+export function isWxBrowser() {
+ const ua = navigator.userAgent.toLowerCase();
+ if (ua.match(/MicroMessenger/i) == 'micromessenger') {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * @description 如果value小于min,取min;如果value大于max,取max
+ * @param {number} min
+ * @param {number} max
+ * @param {number} value
+ */
+export function range(min = 0, max = 0, value = 0) {
+ return Math.max(min, Math.min(max, Number(value)));
+}
diff --git a/sheep/hooks/useApp.js b/sheep/hooks/useApp.js
new file mode 100644
index 00000000..e69de29b
diff --git a/sheep/hooks/useGoods.js b/sheep/hooks/useGoods.js
new file mode 100644
index 00000000..51dd5dc8
--- /dev/null
+++ b/sheep/hooks/useGoods.js
@@ -0,0 +1,137 @@
+import { ref } from 'vue';
+import dayjs from 'dayjs';
+import $url from '@/sheep/url';
+
+// 格式化销量
+export function formatSales(type, num) {
+ num = num + '';
+ if (type === 'exact') {
+ return '已售' + num;
+ } else {
+ if (num < 10) {
+ return '销量≤10';
+ } else {
+ let a = Math.pow(10, num.length - 1);
+ return '已售' + parseInt(num / a) * a + '+';
+ }
+ }
+}
+
+// 格式化兑换量
+export function formatExchange(type, num) {
+ num = num + '';
+ if (type === 'exact') {
+ return '已兑换' + num;
+ } else {
+ if (num < 10) {
+ return '已兑换≤10';
+ } else {
+ let a = Math.pow(10, num.length - 1);
+ return '已兑换' + parseInt(num / a) * a + '+';
+ }
+ }
+}
+
+// 格式化库存
+export function formatStock(type, num) {
+ num = num + '';
+ if (type === 'exact') {
+ return '库存' + num;
+ } else {
+ if (num < 10) {
+ return '库存≤10';
+ } else {
+ let a = Math.pow(10, num.length - 1);
+ return '库存 ' + parseInt(num / a) * a + '+';
+ }
+ }
+}
+
+// 格式化价格
+export function formatPrice(e) {
+ return e.length === 1 ? e[0] : e.join('~');
+}
+
+// 格式化商品轮播
+export function formatGoodsSwiper(list) {
+ let swiper = [];
+ list.forEach((item, key) => {
+ if (item.indexOf('.avi') !== -1 || item.indexOf('.mp4') !== -1) {
+ swiper.push({
+ src: $url.cdn(item),
+ type: 'video',
+ });
+ } else {
+ swiper.push({
+ src: $url.cdn(item),
+ type: 'image',
+ });
+ }
+ });
+ return swiper;
+}
+
+export function formatOrderColor(type) {
+ if (
+ type === 'apply_refund' ||
+ type === 'groupon_ing' ||
+ type === 'nocomment' ||
+ type === 'noget' ||
+ type === 'nosend'
+ ) {
+ return 'warning-color';
+ } else if (
+ type === 'closed' ||
+ type === 'groupon_invalid' ||
+ type === 'cancel' ||
+ type === 'refund_agree'
+ ) {
+ return 'danger-color';
+ } else if (type === 'completed') {
+ return 'success-color';
+ } else if (type === 'unpaid') {
+ return 'info-color';
+ }
+}
+
+// 计算相隔时间
+export function useDurationTime(toTime, fromTime = '') {
+ toTime = getDayjsTime(toTime);
+ if (fromTime === '') {
+ fromTime = dayjs();
+ }
+ let duration = ref(toTime - fromTime);
+ if (duration.value > 0) {
+ setTimeout(() => {
+ if (duration.value > 0) {
+ duration.value -= 1000;
+ }
+ }, 1000);
+ }
+
+ let durationTime = dayjs.duration(duration.value);
+ return {
+ h: (durationTime.months() * 30 * 24 + durationTime.days() * 24 + durationTime.hours())
+ .toString()
+ .padStart(2, '0'),
+ m: durationTime.minutes().toString().padStart(2, '0'),
+ s: durationTime.seconds().toString().padStart(2, '0'),
+ ms: durationTime.$ms,
+ };
+}
+
+function getDayjsTime(time) {
+ time = time.toString();
+ if (time.indexOf('-') > 0) {
+ // 'date'
+ return dayjs(time);
+ }
+ if (time.length > 10) {
+ // 'timestamp'
+ return dayjs(parseInt(time));
+ }
+ if (time.length === 10) {
+ // 'unixtime'
+ return dayjs.unix(parseInt(time));
+ }
+}
diff --git a/sheep/hooks/useModal.js b/sheep/hooks/useModal.js
new file mode 100644
index 00000000..273124e1
--- /dev/null
+++ b/sheep/hooks/useModal.js
@@ -0,0 +1,128 @@
+import $store from '@/sheep/store';
+import $helper from '@/sheep/helper';
+import dayjs from 'dayjs';
+import { ref } from 'vue';
+import test from '@/sheep/helper/test.js';
+import $api from '@/sheep/api';
+
+// 打开授权弹框
+export function showAuthModal(type = 'accountLogin') {
+ const modal = $store('modal');
+ if (modal.auth !== '') {
+ closeAuthModal();
+ setTimeout(() => {
+ modal.$patch((state) => {
+ state.auth = type;
+ });
+ }, 100);
+ } else {
+ modal.$patch((state) => {
+ state.auth = type;
+ });
+ }
+}
+
+// 关闭授权弹框
+export function closeAuthModal() {
+ $store('modal').$patch((state) => {
+ state.auth = '';
+ });
+}
+
+// 打开分享弹框
+export function showShareModal() {
+ $store('modal').$patch((state) => {
+ state.share = true;
+ });
+}
+
+// 关闭分享弹框
+export function closeShareModal() {
+ $store('modal').$patch((state) => {
+ state.share = false;
+ });
+}
+
+// 打开快捷菜单
+export function showMenuTools() {
+ $store('modal').$patch((state) => {
+ state.menu = true;
+ });
+}
+
+// 关闭快捷菜单
+export function closeMenuTools() {
+ $store('modal').$patch((state) => {
+ state.menu = false;
+ });
+}
+
+// 发送短信验证码 60秒
+export function getSmsCode(event, mobile = '') {
+ const modalStore = $store('modal');
+ const lastSendTimer = modalStore.lastTimer[event];
+
+ if (typeof lastSendTimer === 'undefined') {
+ $helper.toast('短信发送事件错误');
+ return;
+ }
+
+ const duration = dayjs().unix() - lastSendTimer;
+ const canSend = duration >= 60;
+
+ if (!canSend) {
+ $helper.toast('请稍后再试');
+ return;
+ }
+
+ if (!test.mobile(mobile)) {
+ $helper.toast('手机号码格式不正确');
+ return;
+ }
+
+ // 发送验证码 + 更新上次发送验证码时间
+ $api.app.sendSms({ mobile, event }).then((res) => {
+ if (res.error === 0) {
+ modalStore.$patch((state) => {
+ state.lastTimer[event] = dayjs().unix();
+ });
+ }
+ });
+
+}
+
+// 获取短信验证码倒计时 -- 60秒
+export function getSmsTimer(event, mobile = '') {
+ const modalStore = $store('modal');
+ const lastSendTimer = modalStore.lastTimer[event];
+
+ if (typeof lastSendTimer === 'undefined') {
+ $helper.toast('短信发送事件错误');
+ return;
+ }
+
+ const duration = ref(dayjs().unix() - lastSendTimer - 60);
+ const canSend = duration.value >= 0;
+
+ if (canSend) {
+ return '获取验证码';
+ }
+
+ if (!canSend) {
+ setTimeout(() => {
+ duration.value++;
+ }, 1000);
+ return -duration.value.toString() + ' 秒';
+ }
+}
+
+// 记录广告弹框历史
+export function saveAdvHistory(adv) {
+ const modal = $store('modal');
+
+ modal.$patch((state) => {
+ if (!state.advHistory.includes(adv.src)) {
+ state.advHistory.push(adv.src);
+ }
+ });
+}
diff --git a/sheep/index.js b/sheep/index.js
new file mode 100644
index 00000000..26e160e6
--- /dev/null
+++ b/sheep/index.js
@@ -0,0 +1,53 @@
+import $api from '@/sheep/api';
+import $url from '@/sheep/url';
+import $router from '@/sheep/router';
+import $platform from '@/sheep/platform';
+import $helper from '@/sheep/helper';
+import zIndex from '@/sheep/config/zIndex.js';
+import $store from '@/sheep/store';
+import dayjs from 'dayjs';
+import relativeTime from 'dayjs/plugin/relativeTime';
+import duration from 'dayjs/plugin/duration';
+import 'dayjs/locale/zh-cn';
+
+dayjs.locale('zh-cn');
+dayjs.extend(relativeTime);
+dayjs.extend(duration);
+
+const sheep = {
+ $api,
+ $store,
+ $url,
+ $router,
+ $platform,
+ $helper,
+ $zIndex: zIndex,
+};
+
+// 加载Shopro底层依赖
+export async function ShoproInit() {
+ // 应用初始化
+ await $store('app').init();
+
+ // 平台初始化加载(各平台provider提供不同的加载流程)
+ $platform.load();
+
+ if (process.env.NODE_ENV === 'development') {
+ ShoproDebug();
+ }
+}
+
+// 开发模式
+function ShoproDebug() {
+ // 开发环境引入vconsole调试
+ // #ifdef H5
+ // import("vconsole").then(vconsole => {
+ // new vconsole.default();
+ // });
+ // #endif
+
+ // 同步前端页面到后端
+ $api.app.pageSync(ROUTES);
+}
+
+export default sheep;
diff --git a/sheep/libs/luch-request/adapters/index.js b/sheep/libs/luch-request/adapters/index.js
new file mode 100644
index 00000000..7688341d
--- /dev/null
+++ b/sheep/libs/luch-request/adapters/index.js
@@ -0,0 +1,101 @@
+import buildURL from '../helpers/buildURL';
+import buildFullPath from '../core/buildFullPath';
+import settle from '../core/settle';
+import { isUndefined } from '../utils';
+
+/**
+ * 返回可选值存在的配置
+ * @param {Array} keys - 可选值数组
+ * @param {Object} config2 - 配置
+ * @return {{}} - 存在的配置项
+ */
+const mergeKeys = (keys, config2) => {
+ const config = {};
+ keys.forEach((prop) => {
+ if (!isUndefined(config2[prop])) {
+ config[prop] = config2[prop];
+ }
+ });
+ return config;
+};
+export default (config) =>
+ new Promise((resolve, reject) => {
+ const fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params);
+ const _config = {
+ url: fullPath,
+ header: config.header,
+ complete: (response) => {
+ config.fullPath = fullPath;
+ response.config = config;
+ try {
+ // 对可能字符串不是json 的情况容错
+ if (typeof response.data === 'string') {
+ response.data = JSON.parse(response.data);
+ }
+ // eslint-disable-next-line no-empty
+ } catch (e) {}
+ settle(resolve, reject, response);
+ },
+ };
+ let requestTask;
+ if (config.method === 'UPLOAD') {
+ delete _config.header['content-type'];
+ delete _config.header['Content-Type'];
+ const otherConfig = {
+ // #ifdef MP-ALIPAY
+ fileType: config.fileType,
+ // #endif
+ filePath: config.filePath,
+ name: config.name,
+ };
+ const optionalKeys = [
+ // #ifdef APP-PLUS || H5
+ 'files',
+ // #endif
+ // #ifdef H5
+ 'file',
+ // #endif
+ // #ifdef H5 || APP-PLUS
+ 'timeout',
+ // #endif
+ 'formData',
+ ];
+ requestTask = uni.uploadFile({
+ ..._config,
+ ...otherConfig,
+ ...mergeKeys(optionalKeys, config),
+ });
+ } else if (config.method === 'DOWNLOAD') {
+ // #ifdef H5 || APP-PLUS
+ if (!isUndefined(config.timeout)) {
+ _config.timeout = config.timeout;
+ }
+ // #endif
+ requestTask = uni.downloadFile(_config);
+ } else {
+ const optionalKeys = [
+ 'data',
+ 'method',
+ // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+ 'timeout',
+ // #endif
+ 'dataType',
+ // #ifndef MP-ALIPAY
+ 'responseType',
+ // #endif
+ // #ifdef APP-PLUS
+ 'sslVerify',
+ // #endif
+ // #ifdef H5
+ 'withCredentials',
+ // #endif
+ // #ifdef APP-PLUS
+ 'firstIpv4',
+ // #endif
+ ];
+ requestTask = uni.request({ ..._config, ...mergeKeys(optionalKeys, config) });
+ }
+ if (config.getTask) {
+ config.getTask(requestTask, config);
+ }
+ });
diff --git a/sheep/libs/luch-request/core/InterceptorManager.js b/sheep/libs/luch-request/core/InterceptorManager.js
new file mode 100644
index 00000000..31fc04b9
--- /dev/null
+++ b/sheep/libs/luch-request/core/InterceptorManager.js
@@ -0,0 +1,50 @@
+'use strict';
+
+function InterceptorManager() {
+ this.handlers = [];
+}
+
+/**
+ * Add a new interceptor to the stack
+ *
+ * @param {Function} fulfilled The function to handle `then` for a `Promise`
+ * @param {Function} rejected The function to handle `reject` for a `Promise`
+ *
+ * @return {Number} An ID used to remove interceptor later
+ */
+InterceptorManager.prototype.use = function use(fulfilled, rejected) {
+ this.handlers.push({
+ fulfilled,
+ rejected,
+ });
+ return this.handlers.length - 1;
+};
+
+/**
+ * Remove an interceptor from the stack
+ *
+ * @param {Number} id The ID that was returned by `use`
+ */
+InterceptorManager.prototype.eject = function eject(id) {
+ if (this.handlers[id]) {
+ this.handlers[id] = null;
+ }
+};
+
+/**
+ * Iterate over all the registered interceptors
+ *
+ * This method is particularly useful for skipping over any
+ * interceptors that may have become `null` calling `eject`.
+ *
+ * @param {Function} fn The function to call for each interceptor
+ */
+InterceptorManager.prototype.forEach = function forEach(fn) {
+ this.handlers.forEach((h) => {
+ if (h !== null) {
+ fn(h);
+ }
+ });
+};
+
+export default InterceptorManager;
diff --git a/sheep/libs/luch-request/core/Request.js b/sheep/libs/luch-request/core/Request.js
new file mode 100644
index 00000000..dd017f19
--- /dev/null
+++ b/sheep/libs/luch-request/core/Request.js
@@ -0,0 +1,198 @@
+/**
+ * @Class Request
+ * @description luch-request http请求插件
+ * @version 3.0.7
+ * @Author lu-ch
+ * @Date 2021-09-04
+ * @Email webwork.s@qq.com
+ * 文档: https://www.quanzhan.co/luch-request/
+ * github: https://github.com/lei-mu/luch-request
+ * DCloud: http://ext.dcloud.net.cn/plugin?id=392
+ * HBuilderX: beat-3.0.4 alpha-3.0.4
+ */
+
+import dispatchRequest from './dispatchRequest';
+import InterceptorManager from './InterceptorManager';
+import mergeConfig from './mergeConfig';
+import defaults from './defaults';
+import { isPlainObject } from '../utils';
+import clone from '../utils/clone';
+
+export default class Request {
+ /**
+ * @param {Object} arg - 全局配置
+ * @param {String} arg.baseURL - 全局根路径
+ * @param {Object} arg.header - 全局header
+ * @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式
+ * @param {String} arg.dataType = [json] - 全局默认的dataType
+ * @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持
+ * @param {Object} arg.custom - 全局默认的自定义参数
+ * @param {Number} arg.timeout - 全局默认的超时时间,单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序(2.10.0)、支付宝小程序
+ * @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持(HBuilderX 2.3.3+)
+ * @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证(cookies)。默认false。仅H5支持(HBuilderX 2.6.15+)
+ * @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+)
+ * @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300
+ */
+ constructor(arg = {}) {
+ if (!isPlainObject(arg)) {
+ arg = {};
+ console.warn('设置全局参数必须接收一个Object');
+ }
+ this.config = clone({ ...defaults, ...arg });
+ this.interceptors = {
+ request: new InterceptorManager(),
+ response: new InterceptorManager(),
+ };
+ }
+
+ /**
+ * @Function
+ * @param {Request~setConfigCallback} f - 设置全局默认配置
+ */
+ setConfig(f) {
+ this.config = f(this.config);
+ }
+
+ middleware(config) {
+ config = mergeConfig(this.config, config);
+ const chain = [dispatchRequest, undefined];
+ let promise = Promise.resolve(config);
+
+ this.interceptors.request.forEach((interceptor) => {
+ chain.unshift(interceptor.fulfilled, interceptor.rejected);
+ });
+
+ this.interceptors.response.forEach((interceptor) => {
+ chain.push(interceptor.fulfilled, interceptor.rejected);
+ });
+
+ while (chain.length) {
+ promise = promise.then(chain.shift(), chain.shift());
+ }
+
+ return promise;
+ }
+
+ /**
+ * @Function
+ * @param {Object} config - 请求配置项
+ * @prop {String} options.url - 请求路径
+ * @prop {Object} options.data - 请求参数
+ * @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型
+ * @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse
+ * @prop {Object} [options.header = config.header] - 请求header
+ * @prop {Object} [options.method = config.method] - 请求方法
+ * @returns {Promise}
+ */
+ request(config = {}) {
+ return this.middleware(config);
+ }
+
+ get(url, options = {}) {
+ return this.middleware({
+ url,
+ method: 'GET',
+ ...options,
+ });
+ }
+
+ post(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'POST',
+ ...options,
+ });
+ }
+
+ // #ifndef MP-ALIPAY
+ put(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'PUT',
+ ...options,
+ });
+ }
+
+ // #endif
+
+ // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+ delete(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'DELETE',
+ ...options,
+ });
+ }
+
+ // #endif
+
+ // #ifdef H5 || MP-WEIXIN
+ connect(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'CONNECT',
+ ...options,
+ });
+ }
+
+ // #endif
+
+ // #ifdef H5 || MP-WEIXIN || MP-BAIDU
+ head(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'HEAD',
+ ...options,
+ });
+ }
+
+ // #endif
+
+ // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+ options(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'OPTIONS',
+ ...options,
+ });
+ }
+
+ // #endif
+
+ // #ifdef H5 || MP-WEIXIN
+ trace(url, data, options = {}) {
+ return this.middleware({
+ url,
+ data,
+ method: 'TRACE',
+ ...options,
+ });
+ }
+
+ // #endif
+
+ upload(url, config = {}) {
+ config.url = url;
+ config.method = 'UPLOAD';
+ return this.middleware(config);
+ }
+
+ download(url, config = {}) {
+ config.url = url;
+ config.method = 'DOWNLOAD';
+ return this.middleware(config);
+ }
+}
+
+/**
+ * setConfig回调
+ * @return {Object} - 返回操作后的config
+ * @callback Request~setConfigCallback
+ * @param {Object} config - 全局默认config
+ */
diff --git a/sheep/libs/luch-request/core/buildFullPath.js b/sheep/libs/luch-request/core/buildFullPath.js
new file mode 100644
index 00000000..849fffa1
--- /dev/null
+++ b/sheep/libs/luch-request/core/buildFullPath.js
@@ -0,0 +1,20 @@
+'use strict';
+
+import isAbsoluteURL from '../helpers/isAbsoluteURL';
+import combineURLs from '../helpers/combineURLs';
+
+/**
+ * Creates a new URL by combining the baseURL with the requestedURL,
+ * only when the requestedURL is not already an absolute URL.
+ * If the requestURL is absolute, this function returns the requestedURL untouched.
+ *
+ * @param {string} baseURL The base URL
+ * @param {string} requestedURL Absolute or relative URL to combine
+ * @returns {string} The combined full path
+ */
+export default function buildFullPath(baseURL, requestedURL) {
+ if (baseURL && !isAbsoluteURL(requestedURL)) {
+ return combineURLs(baseURL, requestedURL);
+ }
+ return requestedURL;
+}
diff --git a/sheep/libs/luch-request/core/defaults.js b/sheep/libs/luch-request/core/defaults.js
new file mode 100644
index 00000000..14db2305
--- /dev/null
+++ b/sheep/libs/luch-request/core/defaults.js
@@ -0,0 +1,29 @@
+/**
+ * 默认的全局配置
+ */
+
+export default {
+ baseURL: '',
+ header: {},
+ method: 'GET',
+ dataType: 'json',
+ // #ifndef MP-ALIPAY
+ responseType: 'text',
+ // #endif
+ custom: {},
+ // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+ timeout: 60000,
+ // #endif
+ // #ifdef APP-PLUS
+ sslVerify: true,
+ // #endif
+ // #ifdef H5
+ withCredentials: false,
+ // #endif
+ // #ifdef APP-PLUS
+ firstIpv4: false,
+ // #endif
+ validateStatus: function validateStatus(status) {
+ return status >= 200 && status < 300;
+ },
+};
diff --git a/sheep/libs/luch-request/core/dispatchRequest.js b/sheep/libs/luch-request/core/dispatchRequest.js
new file mode 100644
index 00000000..884c16c7
--- /dev/null
+++ b/sheep/libs/luch-request/core/dispatchRequest.js
@@ -0,0 +1,3 @@
+import adapter from '../adapters/index';
+
+export default (config) => adapter(config);
diff --git a/sheep/libs/luch-request/core/mergeConfig.js b/sheep/libs/luch-request/core/mergeConfig.js
new file mode 100644
index 00000000..65b913c8
--- /dev/null
+++ b/sheep/libs/luch-request/core/mergeConfig.js
@@ -0,0 +1,103 @@
+import { deepMerge, isUndefined } from '../utils';
+
+/**
+ * 合并局部配置优先的配置,如果局部有该配置项则用局部,如果全局有该配置项则用全局
+ * @param {Array} keys - 配置项
+ * @param {Object} globalsConfig - 当前的全局配置
+ * @param {Object} config2 - 局部配置
+ * @return {{}}
+ */
+const mergeKeys = (keys, globalsConfig, config2) => {
+ const config = {};
+ keys.forEach((prop) => {
+ if (!isUndefined(config2[prop])) {
+ config[prop] = config2[prop];
+ } else if (!isUndefined(globalsConfig[prop])) {
+ config[prop] = globalsConfig[prop];
+ }
+ });
+ return config;
+};
+/**
+ *
+ * @param globalsConfig - 当前实例的全局配置
+ * @param config2 - 当前的局部配置
+ * @return - 合并后的配置
+ */
+export default (globalsConfig, config2 = {}) => {
+ const method = config2.method || globalsConfig.method || 'GET';
+ let config = {
+ baseURL: globalsConfig.baseURL || '',
+ method,
+ url: config2.url || '',
+ params: config2.params || {},
+ custom: { ...(globalsConfig.custom || {}), ...(config2.custom || {}) },
+ header: deepMerge(globalsConfig.header || {}, config2.header || {}),
+ };
+ const defaultToConfig2Keys = ['getTask', 'validateStatus'];
+ config = { ...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2) };
+
+ // eslint-disable-next-line no-empty
+ if (method === 'DOWNLOAD') {
+ // #ifdef H5 || APP-PLUS
+ if (!isUndefined(config2.timeout)) {
+ config.timeout = config2.timeout;
+ } else if (!isUndefined(globalsConfig.timeout)) {
+ config.timeout = globalsConfig.timeout;
+ }
+ // #endif
+ } else if (method === 'UPLOAD') {
+ delete config.header['content-type'];
+ delete config.header['Content-Type'];
+ const uploadKeys = [
+ // #ifdef APP-PLUS || H5
+ 'files',
+ // #endif
+ // #ifdef MP-ALIPAY
+ 'fileType',
+ // #endif
+ // #ifdef H5
+ 'file',
+ // #endif
+ 'filePath',
+ 'name',
+ // #ifdef H5 || APP-PLUS
+ 'timeout',
+ // #endif
+ 'formData',
+ ];
+ uploadKeys.forEach((prop) => {
+ if (!isUndefined(config2[prop])) {
+ config[prop] = config2[prop];
+ }
+ });
+ // #ifdef H5 || APP-PLUS
+ if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) {
+ config.timeout = globalsConfig.timeout;
+ }
+ // #endif
+ } else {
+ const defaultsKeys = [
+ 'data',
+ // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+ 'timeout',
+ // #endif
+ 'dataType',
+ // #ifndef MP-ALIPAY
+ 'responseType',
+ // #endif
+ // #ifdef APP-PLUS
+ 'sslVerify',
+ // #endif
+ // #ifdef H5
+ 'withCredentials',
+ // #endif
+ // #ifdef APP-PLUS
+ 'firstIpv4',
+ // #endif
+ ];
+ config = { ...config, ...mergeKeys(defaultsKeys, globalsConfig, config2) };
+ }
+
+ return config;
+};
diff --git a/sheep/libs/luch-request/core/settle.js b/sheep/libs/luch-request/core/settle.js
new file mode 100644
index 00000000..3fa6359e
--- /dev/null
+++ b/sheep/libs/luch-request/core/settle.js
@@ -0,0 +1,16 @@
+/**
+ * Resolve or reject a Promise based on response status.
+ *
+ * @param {Function} resolve A function that resolves the promise.
+ * @param {Function} reject A function that rejects the promise.
+ * @param {object} response The response.
+ */
+export default function settle(resolve, reject, response) {
+ const { validateStatus } = response.config;
+ const status = response.statusCode;
+ if (status && (!validateStatus || validateStatus(status))) {
+ resolve(response);
+ } else {
+ reject(response);
+ }
+}
diff --git a/sheep/libs/luch-request/helpers/buildURL.js b/sheep/libs/luch-request/helpers/buildURL.js
new file mode 100644
index 00000000..6ea9fc54
--- /dev/null
+++ b/sheep/libs/luch-request/helpers/buildURL.js
@@ -0,0 +1,69 @@
+'use strict';
+
+import * as utils from '../utils';
+
+function encode(val) {
+ return encodeURIComponent(val)
+ .replace(/%40/gi, '@')
+ .replace(/%3A/gi, ':')
+ .replace(/%24/g, '$')
+ .replace(/%2C/gi, ',')
+ .replace(/%20/g, '+')
+ .replace(/%5B/gi, '[')
+ .replace(/%5D/gi, ']');
+}
+
+/**
+ * Build a URL by appending params to the end
+ *
+ * @param {string} url The base of the url (e.g., http://www.google.com)
+ * @param {object} [params] The params to be appended
+ * @returns {string} The formatted url
+ */
+export default function buildURL(url, params) {
+ /* eslint no-param-reassign:0 */
+ if (!params) {
+ return url;
+ }
+
+ let serializedParams;
+ if (utils.isURLSearchParams(params)) {
+ serializedParams = params.toString();
+ } else {
+ const parts = [];
+
+ utils.forEach(params, (val, key) => {
+ if (val === null || typeof val === 'undefined') {
+ return;
+ }
+
+ if (utils.isArray(val)) {
+ key = `${key}[]`;
+ } else {
+ val = [val];
+ }
+
+ utils.forEach(val, (v) => {
+ if (utils.isDate(v)) {
+ v = v.toISOString();
+ } else if (utils.isObject(v)) {
+ v = JSON.stringify(v);
+ }
+ parts.push(`${encode(key)}=${encode(v)}`);
+ });
+ });
+
+ serializedParams = parts.join('&');
+ }
+
+ if (serializedParams) {
+ const hashmarkIndex = url.indexOf('#');
+ if (hashmarkIndex !== -1) {
+ url = url.slice(0, hashmarkIndex);
+ }
+
+ url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
+ }
+
+ return url;
+}
diff --git a/sheep/libs/luch-request/helpers/combineURLs.js b/sheep/libs/luch-request/helpers/combineURLs.js
new file mode 100644
index 00000000..33de8af3
--- /dev/null
+++ b/sheep/libs/luch-request/helpers/combineURLs.js
@@ -0,0 +1,14 @@
+'use strict';
+
+/**
+ * Creates a new URL by combining the specified URLs
+ *
+ * @param {string} baseURL The base URL
+ * @param {string} relativeURL The relative URL
+ * @returns {string} The combined URL
+ */
+export default function combineURLs(baseURL, relativeURL) {
+ return relativeURL
+ ? `${baseURL.replace(/\/+$/, '')}/${relativeURL.replace(/^\/+/, '')}`
+ : baseURL;
+}
diff --git a/sheep/libs/luch-request/helpers/isAbsoluteURL.js b/sheep/libs/luch-request/helpers/isAbsoluteURL.js
new file mode 100644
index 00000000..566097a5
--- /dev/null
+++ b/sheep/libs/luch-request/helpers/isAbsoluteURL.js
@@ -0,0 +1,14 @@
+'use strict';
+
+/**
+ * Determines whether the specified URL is absolute
+ *
+ * @param {string} url The URL to test
+ * @returns {boolean} True if the specified URL is absolute, otherwise false
+ */
+export default function isAbsoluteURL(url) {
+ // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL).
+ // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
+ // by any combination of letters, digits, plus, period, or hyphen.
+ return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url);
+}
diff --git a/sheep/libs/luch-request/index.d.ts b/sheep/libs/luch-request/index.d.ts
new file mode 100644
index 00000000..e939ce13
--- /dev/null
+++ b/sheep/libs/luch-request/index.d.ts
@@ -0,0 +1,116 @@
+type AnyObject = Record
+type HttpPromise = Promise>;
+type Tasks = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask
+export interface RequestTask {
+ abort: () => void;
+ offHeadersReceived: () => void;
+ onHeadersReceived: () => void;
+}
+export interface HttpRequestConfig {
+ /** 请求基地址 */
+ baseURL?: string;
+ /** 请求服务器接口地址 */
+ url?: string;
+
+ /** 请求查询参数,自动拼接为查询字符串 */
+ params?: AnyObject;
+ /** 请求体参数 */
+ data?: AnyObject;
+
+ /** 文件对应的 key */
+ name?: string;
+ /** HTTP 请求中其他额外的 form data */
+ formData?: AnyObject;
+ /** 要上传文件资源的路径。 */
+ filePath?: string;
+ /** 需要上传的文件列表。使用 files 时,filePath 和 name 不生效,App、H5( 2.6.15+) */
+ files?: Array<{
+ name?: string;
+ file?: File;
+ uri: string;
+ }>;
+ /** 要上传的文件对象,仅H5(2.6.15+)支持 */
+ file?: File;
+
+ /** 请求头信息 */
+ header?: AnyObject;
+ /** 请求方式 */
+ method?: "GET" | "POST" | "PUT" | "DELETE" | "CONNECT" | "HEAD" | "OPTIONS" | "TRACE" | "UPLOAD" | "DOWNLOAD";
+ /** 如果设为 json,会尝试对返回的数据做一次 JSON.parse */
+ dataType?: string;
+ /** 设置响应的数据类型,支付宝小程序不支持 */
+ responseType?: "text" | "arraybuffer";
+ /** 自定义参数 */
+ custom?: AnyObject;
+ /** 超时时间,仅微信小程序(2.10.0)、支付宝小程序支持 */
+ timeout?: number;
+ /** DNS解析时优先使用ipv4,仅 App-Android 支持 (HBuilderX 2.8.0+) */
+ firstIpv4?: boolean;
+ /** 验证 ssl 证书 仅5+App安卓端支持(HBuilderX 2.3.3+) */
+ sslVerify?: boolean;
+ /** 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) */
+ withCredentials?: boolean;
+
+ /** 返回当前请求的task, options。请勿在此处修改options。 */
+ getTask?: (task: T, options: HttpRequestConfig) => void;
+ /** 全局自定义验证器 */
+ validateStatus?: (statusCode: number) => boolean | void;
+}
+export interface HttpResponse {
+ config: HttpRequestConfig;
+ statusCode: number;
+ cookies: Array;
+ data: T;
+ errMsg: string;
+ header: AnyObject;
+}
+export interface HttpUploadResponse {
+ config: HttpRequestConfig;
+ statusCode: number;
+ data: T;
+ errMsg: string;
+}
+export interface HttpDownloadResponse extends HttpResponse {
+ tempFilePath: string;
+}
+export interface HttpError {
+ config: HttpRequestConfig;
+ statusCode?: number;
+ cookies?: Array;
+ data?: any;
+ errMsg: string;
+ header?: AnyObject;
+}
+export interface HttpInterceptorManager {
+ use(
+ onFulfilled?: (config: V) => Promise | V,
+ onRejected?: (config: E) => Promise | E
+ ): void;
+ eject(id: number): void;
+}
+export abstract class HttpRequestAbstract {
+ constructor(config?: HttpRequestConfig);
+ config: HttpRequestConfig;
+ interceptors: {
+ request: HttpInterceptorManager;
+ response: HttpInterceptorManager;
+ }
+ middleware(config: HttpRequestConfig): HttpPromise;
+ request(config: HttpRequestConfig): HttpPromise;
+ get(url: string, config?: HttpRequestConfig): HttpPromise;
+ upload(url: string, config?: HttpRequestConfig): HttpPromise;
+ delete(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise;
+ head(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise;
+ post(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise;
+ put(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise;
+ connect(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise;
+ options(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise;
+ trace(url: string, data?: AnyObject, config?: HttpRequestConfig): HttpPromise;
+
+ download(url: string, config?: HttpRequestConfig): Promise;
+
+ setConfig(onSend: (config: HttpRequestConfig) => HttpRequestConfig): void;
+}
+
+declare class HttpRequest extends HttpRequestAbstract { }
+export default HttpRequest;
diff --git a/sheep/libs/luch-request/index.js b/sheep/libs/luch-request/index.js
new file mode 100644
index 00000000..e3ea529e
--- /dev/null
+++ b/sheep/libs/luch-request/index.js
@@ -0,0 +1,3 @@
+import Request from './core/Request';
+
+export default Request;
diff --git a/sheep/libs/luch-request/utils.js b/sheep/libs/luch-request/utils.js
new file mode 100644
index 00000000..2940b301
--- /dev/null
+++ b/sheep/libs/luch-request/utils.js
@@ -0,0 +1,131 @@
+'use strict';
+
+// utils is a library of generic helper functions non-specific to axios
+
+const { toString } = Object.prototype;
+
+/**
+ * Determine if a value is an Array
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Array, otherwise false
+ */
+export function isArray(val) {
+ return toString.call(val) === '[object Array]';
+}
+
+/**
+ * Determine if a value is an Object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Object, otherwise false
+ */
+export function isObject(val) {
+ return val !== null && typeof val === 'object';
+}
+
+/**
+ * Determine if a value is a Date
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a Date, otherwise false
+ */
+export function isDate(val) {
+ return toString.call(val) === '[object Date]';
+}
+
+/**
+ * Determine if a value is a URLSearchParams object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a URLSearchParams object, otherwise false
+ */
+export function isURLSearchParams(val) {
+ return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams;
+}
+
+/**
+ * Iterate over an Array or an Object invoking a function for each item.
+ *
+ * If `obj` is an Array callback will be called passing
+ * the value, index, and complete array for each item.
+ *
+ * If 'obj' is an Object callback will be called passing
+ * the value, key, and complete object for each property.
+ *
+ * @param {Object|Array} obj The object to iterate
+ * @param {Function} fn The callback to invoke for each item
+ */
+export function forEach(obj, fn) {
+ // Don't bother if no value provided
+ if (obj === null || typeof obj === 'undefined') {
+ return;
+ }
+
+ // Force an array if not already something iterable
+ if (typeof obj !== 'object') {
+ /* eslint no-param-reassign:0 */
+ obj = [obj];
+ }
+
+ if (isArray(obj)) {
+ // Iterate over array values
+ for (let i = 0, l = obj.length; i < l; i++) {
+ fn.call(null, obj[i], i, obj);
+ }
+ } else {
+ // Iterate over object keys
+ for (const key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ fn.call(null, obj[key], key, obj);
+ }
+ }
+ }
+}
+
+/**
+ * 是否为boolean 值
+ * @param val
+ * @returns {boolean}
+ */
+export function isBoolean(val) {
+ return typeof val === 'boolean';
+}
+
+/**
+ * 是否为真正的对象{} new Object
+ * @param {any} obj - 检测的对象
+ * @returns {boolean}
+ */
+export function isPlainObject(obj) {
+ return Object.prototype.toString.call(obj) === '[object Object]';
+}
+
+/**
+ * Function equal to merge with the difference being that no reference
+ * to original objects is kept.
+ *
+ * @see merge
+ * @param {Object} obj1 Object to merge
+ * @returns {Object} Result of all merge properties
+ */
+export function deepMerge(/* obj1, obj2, obj3, ... */) {
+ const result = {};
+ function assignValue(val, key) {
+ if (typeof result[key] === 'object' && typeof val === 'object') {
+ result[key] = deepMerge(result[key], val);
+ } else if (typeof val === 'object') {
+ result[key] = deepMerge({}, val);
+ } else {
+ result[key] = val;
+ }
+ }
+ for (let i = 0, l = arguments.length; i < l; i++) {
+ forEach(arguments[i], assignValue);
+ }
+ return result;
+}
+
+export function isUndefined(val) {
+ return typeof val === 'undefined';
+}
diff --git a/sheep/libs/luch-request/utils/clone.js b/sheep/libs/luch-request/utils/clone.js
new file mode 100644
index 00000000..2458752a
--- /dev/null
+++ b/sheep/libs/luch-request/utils/clone.js
@@ -0,0 +1,260 @@
+/* eslint-disable */
+var clone = (function () {
+ 'use strict';
+
+ function _instanceof(obj, type) {
+ return type != null && obj instanceof type;
+ }
+
+ var nativeMap;
+ try {
+ nativeMap = Map;
+ } catch (_) {
+ // maybe a reference error because no `Map`. Give it a dummy value that no
+ // value will ever be an instanceof.
+ nativeMap = function () {};
+ }
+
+ var nativeSet;
+ try {
+ nativeSet = Set;
+ } catch (_) {
+ nativeSet = function () {};
+ }
+
+ var nativePromise;
+ try {
+ nativePromise = Promise;
+ } catch (_) {
+ nativePromise = function () {};
+ }
+
+ /**
+ * Clones (copies) an Object using deep copying.
+ *
+ * This function supports circular references by default, but if you are certain
+ * there are no circular references in your object, you can save some CPU time
+ * by calling clone(obj, false).
+ *
+ * Caution: if `circular` is false and `parent` contains circular references,
+ * your program may enter an infinite loop and crash.
+ *
+ * @param `parent` - the object to be cloned
+ * @param `circular` - set to true if the object to be cloned may contain
+ * circular references. (optional - true by default)
+ * @param `depth` - set to a number if the object is only to be cloned to
+ * a particular depth. (optional - defaults to Infinity)
+ * @param `prototype` - sets the prototype to be used when cloning an object.
+ * (optional - defaults to parent prototype).
+ * @param `includeNonEnumerable` - set to true if the non-enumerable properties
+ * should be cloned as well. Non-enumerable properties on the prototype
+ * chain will be ignored. (optional - false by default)
+ */
+ function clone(parent, circular, depth, prototype, includeNonEnumerable) {
+ if (typeof circular === 'object') {
+ depth = circular.depth;
+ prototype = circular.prototype;
+ includeNonEnumerable = circular.includeNonEnumerable;
+ circular = circular.circular;
+ }
+ // maintain two arrays for circular references, where corresponding parents
+ // and children have the same index
+ var allParents = [];
+ var allChildren = [];
+
+ var useBuffer = typeof Buffer != 'undefined';
+
+ if (typeof circular == 'undefined') circular = true;
+
+ if (typeof depth == 'undefined') depth = Infinity;
+
+ // recurse this function so we don't reset allParents and allChildren
+ function _clone(parent, depth) {
+ // cloning null always returns null
+ if (parent === null) return null;
+
+ if (depth === 0) return parent;
+
+ var child;
+ var proto;
+ if (typeof parent != 'object') {
+ return parent;
+ }
+
+ if (_instanceof(parent, nativeMap)) {
+ child = new nativeMap();
+ } else if (_instanceof(parent, nativeSet)) {
+ child = new nativeSet();
+ } else if (_instanceof(parent, nativePromise)) {
+ child = new nativePromise(function (resolve, reject) {
+ parent.then(
+ function (value) {
+ resolve(_clone(value, depth - 1));
+ },
+ function (err) {
+ reject(_clone(err, depth - 1));
+ },
+ );
+ });
+ } else if (clone.__isArray(parent)) {
+ child = [];
+ } else if (clone.__isRegExp(parent)) {
+ child = new RegExp(parent.source, __getRegExpFlags(parent));
+ if (parent.lastIndex) child.lastIndex = parent.lastIndex;
+ } else if (clone.__isDate(parent)) {
+ child = new Date(parent.getTime());
+ } else if (useBuffer && Buffer.isBuffer(parent)) {
+ if (Buffer.from) {
+ // Node.js >= 5.10.0
+ child = Buffer.from(parent);
+ } else {
+ // Older Node.js versions
+ child = new Buffer(parent.length);
+ parent.copy(child);
+ }
+ return child;
+ } else if (_instanceof(parent, Error)) {
+ child = Object.create(parent);
+ } else {
+ if (typeof prototype == 'undefined') {
+ proto = Object.getPrototypeOf(parent);
+ child = Object.create(proto);
+ } else {
+ child = Object.create(prototype);
+ proto = prototype;
+ }
+ }
+
+ if (circular) {
+ var index = allParents.indexOf(parent);
+
+ if (index != -1) {
+ return allChildren[index];
+ }
+ allParents.push(parent);
+ allChildren.push(child);
+ }
+
+ if (_instanceof(parent, nativeMap)) {
+ parent.forEach(function (value, key) {
+ var keyChild = _clone(key, depth - 1);
+ var valueChild = _clone(value, depth - 1);
+ child.set(keyChild, valueChild);
+ });
+ }
+ if (_instanceof(parent, nativeSet)) {
+ parent.forEach(function (value) {
+ var entryChild = _clone(value, depth - 1);
+ child.add(entryChild);
+ });
+ }
+
+ for (var i in parent) {
+ var attrs = Object.getOwnPropertyDescriptor(parent, i);
+ if (attrs) {
+ child[i] = _clone(parent[i], depth - 1);
+ }
+
+ try {
+ var objProperty = Object.getOwnPropertyDescriptor(parent, i);
+ if (objProperty.set === 'undefined') {
+ // no setter defined. Skip cloning this property
+ continue;
+ }
+ child[i] = _clone(parent[i], depth - 1);
+ } catch (e) {
+ if (e instanceof TypeError) {
+ // when in strict mode, TypeError will be thrown if child[i] property only has a getter
+ // we can't do anything about this, other than inform the user that this property cannot be set.
+ continue;
+ } else if (e instanceof ReferenceError) {
+ //this may happen in non strict mode
+ continue;
+ }
+ }
+ }
+
+ if (Object.getOwnPropertySymbols) {
+ var symbols = Object.getOwnPropertySymbols(parent);
+ for (var i = 0; i < symbols.length; i++) {
+ // Don't need to worry about cloning a symbol because it is a primitive,
+ // like a number or string.
+ var symbol = symbols[i];
+ var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
+ if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
+ continue;
+ }
+ child[symbol] = _clone(parent[symbol], depth - 1);
+ Object.defineProperty(child, symbol, descriptor);
+ }
+ }
+
+ if (includeNonEnumerable) {
+ var allPropertyNames = Object.getOwnPropertyNames(parent);
+ for (var i = 0; i < allPropertyNames.length; i++) {
+ var propertyName = allPropertyNames[i];
+ var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
+ if (descriptor && descriptor.enumerable) {
+ continue;
+ }
+ child[propertyName] = _clone(parent[propertyName], depth - 1);
+ Object.defineProperty(child, propertyName, descriptor);
+ }
+ }
+
+ return child;
+ }
+
+ return _clone(parent, depth);
+ }
+
+ /**
+ * Simple flat clone using prototype, accepts only objects, usefull for property
+ * override on FLAT configuration object (no nested props).
+ *
+ * USE WITH CAUTION! This may not behave as you wish if you do not know how this
+ * works.
+ */
+ clone.clonePrototype = function clonePrototype(parent) {
+ if (parent === null) return null;
+
+ var c = function () {};
+ c.prototype = parent;
+ return new c();
+ };
+
+ // private utility functions
+
+ function __objToStr(o) {
+ return Object.prototype.toString.call(o);
+ }
+ clone.__objToStr = __objToStr;
+
+ function __isDate(o) {
+ return typeof o === 'object' && __objToStr(o) === '[object Date]';
+ }
+ clone.__isDate = __isDate;
+
+ function __isArray(o) {
+ return typeof o === 'object' && __objToStr(o) === '[object Array]';
+ }
+ clone.__isArray = __isArray;
+
+ function __isRegExp(o) {
+ return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
+ }
+ clone.__isRegExp = __isRegExp;
+
+ function __getRegExpFlags(re) {
+ var flags = '';
+ if (re.global) flags += 'g';
+ if (re.ignoreCase) flags += 'i';
+ if (re.multiline) flags += 'm';
+ return flags;
+ }
+ clone.__getRegExpFlags = __getRegExpFlags;
+
+ return clone;
+})();
+
+export default clone;
diff --git a/sheep/libs/permission.js b/sheep/libs/permission.js
new file mode 100644
index 00000000..59f9413b
--- /dev/null
+++ b/sheep/libs/permission.js
@@ -0,0 +1,246 @@
+/// null = 未请求,1 = 已允许,0 = 拒绝|受限, 2 = 系统未开启
+
+var isIOS;
+
+function album() {
+ var result = 0;
+ var PHPhotoLibrary = plus.ios.import('PHPhotoLibrary');
+ var authStatus = PHPhotoLibrary.authorizationStatus();
+ if (authStatus === 0) {
+ result = null;
+ } else if (authStatus == 3) {
+ result = 1;
+ } else {
+ result = 0;
+ }
+ plus.ios.deleteObject(PHPhotoLibrary);
+ return result;
+}
+
+function camera() {
+ var result = 0;
+ var AVCaptureDevice = plus.ios.import('AVCaptureDevice');
+ var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
+ if (authStatus === 0) {
+ result = null;
+ } else if (authStatus == 3) {
+ result = 1;
+ } else {
+ result = 0;
+ }
+ plus.ios.deleteObject(AVCaptureDevice);
+ return result;
+}
+
+function location() {
+ var result = 0;
+ var cllocationManger = plus.ios.import('CLLocationManager');
+ var enable = cllocationManger.locationServicesEnabled();
+ var status = cllocationManger.authorizationStatus();
+ if (!enable) {
+ result = 2;
+ } else if (status === 0) {
+ result = null;
+ } else if (status === 3 || status === 4) {
+ result = 1;
+ } else {
+ result = 0;
+ }
+ plus.ios.deleteObject(cllocationManger);
+ return result;
+}
+
+function push() {
+ var result = 0;
+ var UIApplication = plus.ios.import('UIApplication');
+ var app = UIApplication.sharedApplication();
+ var enabledTypes = 0;
+ if (app.currentUserNotificationSettings) {
+ var settings = app.currentUserNotificationSettings();
+ enabledTypes = settings.plusGetAttribute('types');
+ if (enabledTypes == 0) {
+ result = 0;
+ console.log('推送权限没有开启');
+ } else {
+ result = 1;
+ console.log('已经开启推送功能!');
+ }
+ plus.ios.deleteObject(settings);
+ } else {
+ enabledTypes = app.enabledRemoteNotificationTypes();
+ if (enabledTypes == 0) {
+ result = 3;
+ console.log('推送权限没有开启!');
+ } else {
+ result = 4;
+ console.log('已经开启推送功能!');
+ }
+ }
+ plus.ios.deleteObject(app);
+ plus.ios.deleteObject(UIApplication);
+ return result;
+}
+
+function contact() {
+ var result = 0;
+ var CNContactStore = plus.ios.import('CNContactStore');
+ var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
+ if (cnAuthStatus === 0) {
+ result = null;
+ } else if (cnAuthStatus == 3) {
+ result = 1;
+ } else {
+ result = 0;
+ }
+ plus.ios.deleteObject(CNContactStore);
+ return result;
+}
+
+function record() {
+ var result = null;
+ var avaudiosession = plus.ios.import('AVAudioSession');
+ var avaudio = avaudiosession.sharedInstance();
+ var status = avaudio.recordPermission();
+ console.log('permissionStatus:' + status);
+ if (status === 1970168948) {
+ result = null;
+ } else if (status === 1735552628) {
+ result = 1;
+ } else {
+ result = 0;
+ }
+ plus.ios.deleteObject(avaudiosession);
+ return result;
+}
+
+function calendar() {
+ var result = null;
+ var EKEventStore = plus.ios.import('EKEventStore');
+ var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
+ if (ekAuthStatus == 3) {
+ result = 1;
+ console.log('日历权限已经开启');
+ } else {
+ console.log('日历权限没有开启');
+ }
+ plus.ios.deleteObject(EKEventStore);
+ return result;
+}
+
+function memo() {
+ var result = null;
+ var EKEventStore = plus.ios.import('EKEventStore');
+ var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
+ if (ekAuthStatus == 3) {
+ result = 1;
+ console.log('备忘录权限已经开启');
+ } else {
+ console.log('备忘录权限没有开启');
+ }
+ plus.ios.deleteObject(EKEventStore);
+ return result;
+}
+
+function requestIOS(permissionID) {
+ return new Promise((resolve, reject) => {
+ switch (permissionID) {
+ case 'push':
+ resolve(push());
+ break;
+ case 'location':
+ resolve(location());
+ break;
+ case 'record':
+ resolve(record());
+ break;
+ case 'camera':
+ resolve(camera());
+ break;
+ case 'album':
+ resolve(album());
+ break;
+ case 'contact':
+ resolve(contact());
+ break;
+ case 'calendar':
+ resolve(calendar());
+ break;
+ case 'memo':
+ resolve(memo());
+ break;
+ default:
+ resolve(0);
+ break;
+ }
+ });
+}
+
+function requestAndroid(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);
+ },
+ function (error) {
+ console.log('result error: ' + error.message);
+ resolve({
+ code: error.code,
+ message: error.message,
+ });
+ },
+ );
+ });
+}
+
+function gotoAppPermissionSetting() {
+ if (permission.isIOS) {
+ var UIApplication = plus.ios.import('UIApplication');
+ var application2 = UIApplication.sharedApplication();
+ var NSURL2 = plus.ios.import('NSURL');
+ var setting2 = NSURL2.URLWithString('app-settings:');
+ application2.openURL(setting2);
+ plus.ios.deleteObject(setting2);
+ plus.ios.deleteObject(NSURL2);
+ plus.ios.deleteObject(application2);
+ } else {
+ var Intent = plus.android.importClass('android.content.Intent');
+ var Settings = plus.android.importClass('android.provider.Settings');
+ var Uri = plus.android.importClass('android.net.Uri');
+ var mainActivity = plus.android.runtimeMainActivity();
+ var intent = new Intent();
+ intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ var uri = Uri.fromParts('package', mainActivity.getPackageName(), null);
+ intent.setData(uri);
+ mainActivity.startActivity(intent);
+ }
+}
+
+const permission = {
+ get isIOS() {
+ return typeof isIOS === 'boolean'
+ ? isIOS
+ : (isIOS = uni.getSystemInfoSync().platform === 'ios');
+ },
+ requestIOS: requestIOS,
+ requestAndroid: requestAndroid,
+ gotoAppSetting: gotoAppPermissionSetting,
+};
+
+export default permission;
diff --git a/sheep/libs/sdk-h5-weixin.js b/sheep/libs/sdk-h5-weixin.js
new file mode 100644
index 00000000..8ac8c609
--- /dev/null
+++ b/sheep/libs/sdk-h5-weixin.js
@@ -0,0 +1,186 @@
+/**
+ * 本模块封装微信浏览器下的一些方法。
+ * 更多微信网页开发sdk方法,详见:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
+ */
+
+import jweixin from 'weixin-js-sdk';
+import $helper from '@/sheep/helper';
+import third from '@/sheep/api/third';
+
+let configSuccess = false;
+
+export default {
+ //判断是否在微信中
+ isWechat() {
+ const ua = window.navigator.userAgent.toLowerCase();
+ if (ua.match(/micromessenger/i) == 'micromessenger') {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ isReady(api) {
+ jweixin.ready(api);
+ },
+
+ // 初始化JSSDK
+ async init(callback) {
+ if (!this.isWechat()) {
+ $helper.toast('请使用微信网页浏览器打开');
+ return;
+ }
+
+ const url = location.href.split('#')[0];
+
+ const { error, data } = await third.wechat.jssdk({
+ platform: 'officialAccount',
+ payload: encodeURIComponent(
+ JSON.stringify({
+ url,
+ }),
+ ),
+ });
+
+ if (error === 0) {
+ jweixin.config({
+ debug: false,
+ appId: data.appId,
+ timestamp: data.timestamp,
+ nonceStr: data.nonceStr,
+ signature: data.signature,
+ jsApiList: data.jsApiList,
+ openTagList: data.openTagList,
+ });
+ }
+
+ configSuccess = true;
+
+ jweixin.error((err) => {
+ configSuccess = false;
+ // $helper.toast('微信JSSDK:' + err.errMsg);
+ });
+
+ if (callback) {
+ callback(data);
+ }
+ },
+
+ //在需要定位页面调用
+ getLocation(callback) {
+ this.isReady(() => {
+ jweixin.getLocation({
+ type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
+ success: function (res) {
+ callback(res);
+ },
+ fail: function (res) {
+ console.log('%c微信H5sdk,getLocation失败:', 'color:green;background:yellow');
+ },
+ });
+ });
+ },
+
+ //获取微信收货地址
+ openAddress(callback) {
+ this.isReady(() => {
+ jweixin.openAddress({
+ success: function (res) {
+ callback.success && callback.success(res);
+ },
+ fail: function (err) {
+ callback.error && callback.error(err);
+ console.log('%c微信H5sdk,openAddress失败:', 'color:green;background:yellow');
+ },
+ complete: function (res) {},
+ });
+ });
+ },
+
+ // 微信扫码
+ scanQRCode(callback) {
+ this.isReady(() => {
+ jweixin.scanQRCode({
+ needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
+ scanType: ['qrCode', 'barCode'], // 可以指定扫二维码还是一维码,默认二者都有
+ success: function (res) {
+ callback(res);
+ },
+ fail: function (res) {
+ console.log('%c微信H5sdk,scanQRCode失败:', 'color:green;background:yellow');
+ },
+ });
+ });
+ },
+
+ // 更新微信分享信息
+ updateShareInfo(data, callback = null) {
+ this.isReady(() => {
+ const shareData = {
+ title: data.title,
+ desc: data.desc,
+ link: data.link,
+ imgUrl: data.image,
+ success: function (res) {
+ if (callback) {
+ callback(res);
+ }
+ // 分享后的一些操作,比如分享统计等等
+ },
+ cancel: function (res) {},
+ };
+
+ // 新版 分享聊天api
+ jweixin.updateAppMessageShareData(shareData);
+ // 新版 分享到朋友圈api
+ jweixin.updateTimelineShareData(shareData);
+ });
+ },
+
+ // 打开坐标位置
+ openLocation(data, callback) {
+ this.isReady(() => {
+ jweixin.openLocation({
+ //根据传入的坐标打开地图
+ latitude: data.latitude,
+ longitude: data.longitude,
+ });
+ });
+ },
+
+ // 选择图片
+ chooseImage(callback) {
+ this.isReady(() => {
+ jweixin.chooseImage({
+ count: 1,
+ sizeType: ['compressed'],
+ sourceType: ['album'],
+ success: function (rs) {
+ callback(rs);
+ },
+ });
+ });
+ },
+
+ //微信支付
+ wxpay(data, callback) {
+ this.isReady(() => {
+ jweixin.chooseWXPay({
+ timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
+ nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
+ package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
+ signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
+ paySign: data.paySign, // 支付签名
+ success: function (res) {
+ callback.success && callback.success(res);
+ },
+ fail: function (err) {
+ callback.fail && callback.fail(err);
+ },
+ cancel: function (err) {
+ callback.cancel && callback.cancel(err);
+ },
+ });
+ });
+ },
+};
diff --git a/sheep/platform/index.js b/sheep/platform/index.js
new file mode 100644
index 00000000..36a54fc6
--- /dev/null
+++ b/sheep/platform/index.js
@@ -0,0 +1,175 @@
+/**
+ * Shopro 第三方平台功能聚合
+ * @version 1.0.3
+ * @author lidongtony
+ * @param {String} name - 厂商+平台名称
+ * @param {String} provider - 厂商
+ * @param {String} platform - 平台名称
+ * @param {String} os - 系统型号
+ * @param {Object} device - 设备信息
+ */
+
+import { isEmpty } from 'lodash';
+// #ifdef H5
+import { isWxBrowser } from '@/sheep/helper/utils';
+// #endif
+import wechat from './provider/wechat/index.js';
+import apple from './provider/apple';
+import share from './share';
+import Pay from './pay';
+
+const device = uni.getSystemInfoSync();
+
+const os = device.platform;
+
+let name = '';
+let provider = '';
+let platform = '';
+let isWechatInstalled = true;
+
+// #ifdef H5
+if (isWxBrowser()) {
+ name = 'WechatOfficialAccount';
+ provider = 'wechat';
+ platform = 'officialAccount';
+} else {
+ name = 'H5';
+ platform = 'h5';
+}
+// #endif
+
+// #ifdef APP-PLUS
+name = 'App';
+platform = 'openPlatform';
+// 检查微信客户端是否安装,否则AppleStore会因此拒绝上架
+if (os === 'ios') {
+ isWechatInstalled = plus.ios.import('WXApi').isWXAppInstalled();
+}
+// #endif
+
+// #ifdef MP-WEIXIN
+name = 'WechatMiniProgram';
+platform = 'miniProgram';
+provider = 'wechat';
+// #endif
+
+if (isEmpty(name)) {
+ uni.showToast({
+ title: '暂不支持该平台',
+ icon: 'none',
+ });
+}
+
+// 加载当前平台前置行为
+const load = () => {
+ if (provider === 'wechat') {
+ wechat.load();
+ }
+};
+
+// 使用厂商独占sdk name = 'wechat' | 'alipay' | 'apple'
+const useProvider = (_provider = '') => {
+ if (_provider === '') _provider = provider;
+ if (_provider === 'wechat') return wechat;
+ if (_provider === 'apple') return apple;
+};
+
+// 支付服务转发
+const pay = (payment, orderType, orderSN) => {
+ return new Pay(payment, orderType, orderSN);
+};
+
+/**
+ * 检查更新 (只检查小程序和App)
+ * @param {Boolean} silence - 静默检查
+ */
+const checkUpdate = (silence = false) => {
+ let canUpdate;
+ // #ifdef MP-WEIXIN
+ useProvider().checkUpdate(silence);
+ // #endif
+
+ // #ifdef APP-PLUS
+ // TODO: 热更新
+ // #endif
+};
+
+/**
+ * 检查网络
+ * @param {Boolean} silence - 静默检查
+ */
+async function checkNetwork() {
+ const networkStatus = await uni.getNetworkType();
+ if (networkStatus.networkType == 'none') {
+ return Promise.resolve(false);
+ }
+ return Promise.resolve(true);
+}
+
+// 获取小程序胶囊信息
+const getCapsule = () => {
+ // #ifdef MP
+ let capsule = uni.getMenuButtonBoundingClientRect();
+ if (!capsule) {
+ capsule = {
+ bottom: 56,
+ height: 32,
+ left: 278,
+ right: 365,
+ top: 24,
+ width: 87,
+ };
+ }
+ return capsule;
+ // #endif
+
+ // #ifndef MP
+ return {
+ bottom: 56,
+ height: 32,
+ left: 278,
+ right: 365,
+ top: 24,
+ width: 87,
+ };
+ // #endif
+};
+
+const capsule = getCapsule();
+
+// 标题栏高度
+const getNavBar = () => {
+ return device.statusBarHeight + 44;
+};
+const navbar = getNavBar();
+
+function getLandingPage() {
+ let page = '';
+ // #ifdef H5
+ page = location.href.split('?')[0];
+ // #endif
+ return page;
+}
+
+// 设置ios+公众号网页落地页 解决微信sdk签名问题
+const landingPage = getLandingPage();
+
+const _platform = {
+ name,
+ device,
+ os,
+ provider,
+ platform,
+ useProvider,
+ checkUpdate,
+ checkNetwork,
+ pay,
+ share,
+ load,
+ capsule,
+ navbar,
+ landingPage,
+ isWechatInstalled,
+};
+
+export default _platform;
diff --git a/sheep/platform/pay.js b/sheep/platform/pay.js
new file mode 100644
index 00000000..c4e0ae9a
--- /dev/null
+++ b/sheep/platform/pay.js
@@ -0,0 +1,239 @@
+import sheep from '@/sheep';
+// #ifdef H5
+import $wxsdk from '@/sheep/libs/sdk-h5-weixin';
+// #endif
+import { getRootUrl } from '@/sheep/helper';
+
+/**
+ * 支付
+ *
+ * @param {String} payment = ['wechat','alipay','wallet'] - 支付方式
+ * @param {String} orderType = ['goods','recharge','groupon'] - 订单类型
+ * @param {String} orderSN - 订单号
+ */
+
+export default class SheepPay {
+ constructor(payment, orderType, orderSN) {
+ this.payment = payment;
+ this.orderSN = orderSN;
+ this.orderType = orderType;
+ this.payAction();
+ }
+
+ payAction() {
+ const payAction = {
+ WechatOfficialAccount: {
+ wechat: () => {
+ this.wechatOfficialAccountPay();
+ },
+ alipay: () => {
+ this.redirectPay(); // 现在公众号可以直接跳转支付宝页面
+ },
+ money: () => {
+ this.moneyPay();
+ },
+ },
+ WechatMiniProgram: {
+ wechat: () => {
+ this.wechatMiniProgramPay();
+ },
+ alipay: () => {
+ this.copyPayLink();
+ },
+ money: () => {
+ this.moneyPay();
+ },
+ },
+ App: {
+ wechat: () => {
+ this.wechatAppPay();
+ },
+ alipay: () => {
+ this.alipay();
+ },
+ money: () => {
+ this.moneyPay();
+ },
+ },
+ H5: {
+ wechat: () => {
+ this.wechatWapPay();
+ },
+ alipay: () => {
+ this.redirectPay();
+ },
+ money: () => {
+ this.moneyPay();
+ },
+ },
+ };
+ return payAction[sheep.$platform.name][this.payment]();
+ }
+
+ // 预支付
+ prepay() {
+ return new Promise((resolve, reject) => {
+ let data = {
+ order_sn: this.orderSN,
+ payment: this.payment,
+ };
+ if (uni.getStorageSync('openid')) {
+ data.openid = uni.getStorageSync('openid');
+ }
+ sheep.$api.pay.prepay(data).then((res) => {
+ res.error === 0 && resolve(res);
+ if (res.error === -1 && res.msg === 'miss_openid') {
+ uni.showModal({
+ title: '微信支付',
+ content: '请先绑定微信再使用微信支付',
+ success: function (res) {
+ if (res.confirm) {
+ sheep.$platform.useProvider('wechat').bind();
+ }
+ },
+ });
+ }
+ });
+ });
+ }
+ // #ifdef H5
+ // 微信公众号JSSDK支付
+ async wechatOfficialAccountPay() {
+ let that = this;
+ let { error, data, msg } = await this.prepay();
+ if (error !== 0) {
+ console.log('支付错误', msg);
+ return;
+ }
+ $wxsdk.wxpay(data.pay_data, {
+ success: () => {
+ that.payResult('success');
+ },
+ cancel: () => {
+ sheep.$helper.toast('支付已手动取消');
+ },
+ fail: () => {
+ that.payResult('fail');
+ },
+ });
+ }
+
+ //浏览器微信H5支付
+ async wechatWapPay() {
+ const { error, data } = await this.prepay();
+ if (error === 0) {
+ const redirect_url = `${getRootUrl()}pages/pay/result?orderSN=${this.orderSN}&payment=${
+ this.payment
+ }`;
+ location.href = `${data.pay_data.h5_url}&redirect_url=${encodeURIComponent(redirect_url)}`;
+ }
+ }
+
+ // 支付链接
+ async redirectPay() {
+ let { error, data } = await this.prepay();
+ if (error === 0) {
+ const redirect_url = `${getRootUrl()}pages/pay/result?orderSN=${this.orderSN}&payment=${
+ this.payment
+ }`;
+ location.href = data.pay_data + encodeURIComponent(redirect_url);
+ }
+ }
+
+ // #endif
+
+ // 微信小程序支付
+ async wechatMiniProgramPay() {
+ let that = this;
+ let result = await this.prepay();
+ uni.requestPayment({
+ provider: 'wxpay',
+ ...result.data.pay_data,
+ success: (res) => {
+ that.payResult('success');
+ },
+ fail: (err) => {
+ if (err.errMsg === 'requestPayment:fail cancel') {
+ sheep.$helper.toast('支付已手动取消');
+ } else {
+ that.payResult('fail');
+ }
+ },
+ });
+ }
+
+ // 余额支付
+ async moneyPay() {
+ const { error } = await this.prepay();
+ error === 0 && this.payResult('success');
+ }
+
+ // 支付宝复制链接支付
+ async copyPayLink() {
+ let that = this;
+ let { error, data } = await this.prepay();
+ if (error === 0) {
+ // 引入showModal 点击确认 复制链接;
+ uni.showModal({
+ title: '支付宝支付',
+ content: '复制链接到外部浏览器',
+ confirmText: '复制链接',
+ success: (res) => {
+ if (res.confirm) {
+ sheep.$helper.copyText(data.pay_data);
+ }
+ },
+ });
+ }
+ }
+
+ // 支付宝支付
+ async alipay() {
+ let that = this;
+ const { error, data } = await this.prepay();
+ if (error === 0) {
+ uni.requestPayment({
+ provider: 'alipay',
+ orderInfo: data.pay_data, //支付宝订单数据
+ success: (res) => {
+ that.payResult('success');
+ },
+ fail: (err) => {
+ if (err.errMsg === 'requestPayment:fail [paymentAlipay:62001]user cancel') {
+ sheep.$helper.toast('支付已手动取消');
+ } else {
+ that.payResult('fail');
+ }
+ },
+ });
+ }
+ }
+
+ // 微信支付
+ async wechatAppPay() {
+ let that = this;
+ let { error, data } = await this.prepay();
+ if (error === 0) {
+ uni.requestPayment({
+ provider: 'wxpay',
+ orderInfo: data.pay_data, //微信订单数据(官方说是string。实测为object)
+ success: (res) => {
+ that.payResult('success');
+ },
+ fail: (err) => {
+ err.errMsg !== 'requestPayment:fail cancel' && that.payResult('fail');
+ },
+ });
+ }
+ }
+
+ // 支付结果跳转,success:成功,fail:失败
+ payResult(resultType) {
+ sheep.$router.redirect('/pages/pay/result', {
+ orderSN: this.orderSN,
+ payment: this.payment, //重新支付的时候使用
+ payState: resultType,
+ orderType: this.orderType,
+ });
+ }
+}
diff --git a/sheep/platform/provider/apple/app.js b/sheep/platform/provider/apple/app.js
new file mode 100644
index 00000000..d73c2e83
--- /dev/null
+++ b/sheep/platform/provider/apple/app.js
@@ -0,0 +1,35 @@
+import third from '@/sheep/api/third';
+
+const login = () => {
+ return new Promise(async (resolve, reject) => {
+ const loginRes = await uni.login({
+ provider: 'apple',
+ success: () => {
+ uni.getUserInfo({
+ provider: 'apple',
+ success: async (res) => {
+ if (res.errMsg === 'getUserInfo:ok') {
+ const payload = res.userInfo;
+ const { error } = await third.apple.login({
+ payload,
+ shareInfo: uni.getStorageSync('shareLog') || {},
+ });
+ if (error === 0) {
+ resolve(true);
+ } else {
+ resolve(false);
+ }
+ }
+ },
+ });
+ },
+ fail: (err) => {
+ resolve(false);
+ },
+ });
+ });
+};
+
+export default {
+ login,
+};
diff --git a/sheep/platform/provider/apple/index.js b/sheep/platform/provider/apple/index.js
new file mode 100644
index 00000000..388c0939
--- /dev/null
+++ b/sheep/platform/provider/apple/index.js
@@ -0,0 +1,9 @@
+// #ifdef APP-PLUS
+import service from './app';
+// #endif
+
+let apple = {};
+if (typeof service !== 'undefined') {
+ apple = service;
+}
+export default apple;
diff --git a/sheep/platform/provider/wechat/index.js b/sheep/platform/provider/wechat/index.js
new file mode 100644
index 00000000..3bb2c7fe
--- /dev/null
+++ b/sheep/platform/provider/wechat/index.js
@@ -0,0 +1,15 @@
+// #ifdef H5
+import service from './officialAccount';
+// #endif
+
+// #ifdef MP-WEIXIN
+import service from './miniProgram';
+// #endif
+
+// #ifdef APP-PLUS
+import service from './openPlatform';
+// #endif
+
+const wechat = service;
+
+export default wechat;
diff --git a/sheep/platform/provider/wechat/miniProgram.js b/sheep/platform/provider/wechat/miniProgram.js
new file mode 100644
index 00000000..b2ce37be
--- /dev/null
+++ b/sheep/platform/provider/wechat/miniProgram.js
@@ -0,0 +1,223 @@
+import { isEmpty } from 'lodash';
+import third from '@/sheep/api/third';
+import $store from '@/sheep/store';
+
+let sessionId = uni.getStorageSync('sessionId');
+let subscribeEventList = [];
+
+// 加载微信小程序
+function load() {
+ checkUpdate();
+ // const sessionStatus = await checkSession();
+ // 小程序的接口改动太频繁了 强制每次进入都重新获取
+ const sessionStatus = false;
+ if (!sessionStatus) {
+ getSessionId();
+ }
+ getSubscribeTemplate();
+}
+
+// 微信小程序授权登陆
+const login = async (e) => {
+ return new Promise(async (resolve, reject) => {
+ if (e.errMsg !== 'getPhoneNumber:ok') {
+ resolve(false);
+ return;
+ }
+
+ const { error } = await third.wechat.login({
+ platform: 'miniProgram',
+ shareInfo: uni.getStorageSync('shareLog') || {},
+ payload: encodeURIComponent(
+ JSON.stringify({
+ sessionId: uni.getStorageSync('sessionId'),
+ code: e.code,
+ iv: e.iv,
+ encryptedData: e.encryptedData,
+ }),
+ ),
+ });
+
+ if (error === 0) {
+ resolve(true);
+ }
+
+ if (error === -1) {
+ getSessionId();
+ }
+ resolve(false);
+ });
+};
+
+// 微信小程序绑定
+const bind = () => {
+ return new Promise(async (resolve, reject) => {
+ const loginRes = await third.wechat.bind({
+ platform: 'miniProgram',
+ payload: encodeURIComponent(
+ JSON.stringify({
+ sessionId: uni.getStorageSync('sessionId'),
+ }),
+ ),
+ });
+
+ if (loginRes.error === -1) {
+ getSessionId();
+ } else if (loginRes.error === 0) {
+ resolve(true);
+ } else {
+ reject(false);
+ }
+ });
+};
+
+// 微信小程序解除绑定
+const unbind = async () => {
+ const { error } = await third.wechat.unbind({
+ platform: 'miniProgram',
+ });
+ return !error;
+};
+
+// 获取最新sessionId
+const getSessionId = async () => {
+ // 获取code
+ let code = '';
+ const loginResult = await uni.login();
+ if (loginResult.errMsg === 'login:ok') {
+ code = loginResult.code;
+ } else {
+ getSessionId();
+ return false;
+ }
+
+ const { error, data } = await third.wechat.getSessionId({
+ platform: 'miniProgram',
+ payload: encodeURIComponent(
+ JSON.stringify({
+ code,
+ auto_login: !!($store('app').platform.auto_login && !$store('user').isLogin),
+ }),
+ ),
+ });
+ if (error === 0) {
+ uni.setStorageSync('sessionId', data.session_id);
+ return true;
+ }
+ return false;
+};
+
+// 检查sessionId是否可用
+const checkSession = () => {
+ return new Promise((resolve, reject) => {
+ if (!sessionId) {
+ return resolve(false);
+ }
+ uni.checkSession({
+ success() {
+ return resolve(true);
+ },
+ fail() {
+ uni.removeStorageSync('sessionId');
+ return resolve(false);
+ },
+ });
+ });
+};
+
+// 小程序更新
+const checkUpdate = async (silence = true) => {
+ if (uni.canIUse('getUpdateManager')) {
+ const updateManager = uni.getUpdateManager();
+ updateManager.onCheckForUpdate(function (res) {
+ // 请求完新版本信息的回调
+ if (res.hasUpdate) {
+ updateManager.onUpdateReady(function () {
+ uni.showModal({
+ title: '更新提示',
+ content: '新版本已经准备好,是否重启应用?',
+ success: function (res) {
+ if (res.confirm) {
+ // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
+ updateManager.applyUpdate();
+ }
+ },
+ });
+ });
+ updateManager.onUpdateFailed(function () {
+ // 新的版本下载失败
+ // uni.showModal({
+ // title: '已经有新版本了哟~',
+ // content: '新版本已经上线啦,请您删除当前小程序,重新搜索打开~',
+ // });
+ });
+ } else {
+ if (!silence) {
+ uni.showModal({
+ title: '当前为最新版本',
+ showCancel: false,
+ });
+ }
+ }
+ });
+ }
+};
+
+// 绑定用户手机号
+const bindUserPhoneNumber = (e) => {
+ return new Promise(async (resolve, reject) => {
+ const { error } = await third.wechat.bindUserPhoneNumber({
+ platform: 'miniProgram',
+ payload: encodeURIComponent(
+ JSON.stringify({
+ code: e.code,
+ iv: e.iv,
+ encryptedData: e.encryptedData,
+ }),
+ ),
+ });
+ if (error === 0) {
+ resolve(true);
+ }
+ resolve(false);
+ });
+};
+
+// 获取订阅消息模板
+async function getSubscribeTemplate() {
+ const { error, data } = await third.wechat.subscribeTemplate();
+ if (error === 0) {
+ subscribeEventList = data;
+ }
+}
+
+// 订阅消息
+function subscribeMessage(event) {
+ let tmplIds = [];
+ if (typeof event === 'string') {
+ tmplIds.push(subscribeEventList[event]);
+ }
+ if (typeof event === 'object') {
+ event.forEach((item) => {
+ if (typeof subscribeEventList[item] !== 'undefined') tmplIds.push(subscribeEventList[item]);
+ });
+ }
+ if (tmplIds.length === 0) return;
+
+ uni.requestSubscribeMessage({
+ tmplIds,
+ fail: (err) => {
+ console.log(err);
+ },
+ });
+}
+
+export default {
+ load,
+ login,
+ bind,
+ unbind,
+ checkUpdate,
+ bindUserPhoneNumber,
+ subscribeMessage,
+};
diff --git a/sheep/platform/provider/wechat/officialAccount.js b/sheep/platform/provider/wechat/officialAccount.js
new file mode 100644
index 00000000..044cd5a8
--- /dev/null
+++ b/sheep/platform/provider/wechat/officialAccount.js
@@ -0,0 +1,112 @@
+import third from '@/sheep/api/third';
+import $wxsdk from '@/sheep/libs/sdk-h5-weixin';
+import $store from '@/sheep/store';
+import $platform from '@/sheep/platform';
+import { getRootUrl } from '@/sheep/helper';
+
+// 加载微信公众号JSSDK
+async function load() {
+ if (
+ $store('app').platform.auto_login &&
+ !$store('user').isLogin &&
+ location.href.search('pages/index/login') === -1
+ ) {
+ // 发起自动登陆
+ login();
+ }
+ $wxsdk.init();
+}
+
+// 微信公众号登陆
+async function login(code = '') {
+ // 获取登陆地址
+ if (!code) {
+ const loginResult = await getLoginUrl();
+ if (loginResult.error === 0 && loginResult.data.login_url) {
+ uni.setStorageSync('returnUrl', location.href);
+ window.location = loginResult.data.login_url;
+ }
+ } else {
+ // 解密code发起登陆
+ const loginResult = await loginByCode(code);
+ if (loginResult.error === 0) {
+ return loginResult;
+ }
+ }
+ return false;
+}
+
+// 微信公众号绑定
+async function bind(code = '') {
+ // 获取绑定地址
+ if (code === '') {
+ const loginResult = await getLoginUrl('bind');
+ if (loginResult.error === 0 && loginResult.data.login_url) {
+ uni.setStorageSync('returnUrl', location.href);
+ window.location = loginResult.data.login_url;
+ }
+ } else {
+ // 解密code发起登陆
+ const loginResult = await bindByCode(code);
+ if (loginResult.error === 0) {
+ return loginResult;
+ }
+ }
+ return false;
+}
+
+// 微信公众号解除绑定
+async function unbind() {
+ const { error } = await third.wechat.unbind({
+ platform: 'officialAccount',
+ });
+ return !error;
+}
+
+// 获取公众号登陆地址
+function getLoginUrl(event = 'login') {
+ let page = getRootUrl() + 'pages/index/login';
+
+ return third.wechat.oauthLogin({
+ platform: 'officialAccount',
+ payload: encodeURIComponent(
+ JSON.stringify({
+ page,
+ event,
+ }),
+ ),
+ });
+}
+
+// 此处使用前端发送code在后端解密,防止用户在后端过长时间停留
+function loginByCode(code) {
+ return third.wechat.login({
+ platform: 'officialAccount',
+ shareInfo: uni.getStorageSync('shareLog') || {},
+ payload: encodeURIComponent(
+ JSON.stringify({
+ code,
+ }),
+ ),
+ });
+}
+
+// 此处使用前端发送code在后端解密,防止用户在后端过长时间停留
+function bindByCode(code) {
+ return third.wechat.bind({
+ platform: 'officialAccount',
+ payload: encodeURIComponent(
+ JSON.stringify({
+ code,
+ }),
+ ),
+ });
+}
+
+export default {
+ load,
+ login,
+ bind,
+ unbind,
+ jssdk: $wxsdk,
+};
diff --git a/sheep/platform/provider/wechat/openPlatform.js b/sheep/platform/provider/wechat/openPlatform.js
new file mode 100644
index 00000000..e1e493a9
--- /dev/null
+++ b/sheep/platform/provider/wechat/openPlatform.js
@@ -0,0 +1,41 @@
+// 登录
+import { isEmpty } from 'lodash';
+import third from '@/sheep/api/third';
+
+const load = async () => {};
+
+// 微信开放平台移动应用授权登陆
+const login = () => {
+ return new Promise(async (resolve, reject) => {
+ const loginRes = await uni.login({
+ provider: 'weixin',
+ onlyAuthorize: true,
+ });
+ if (loginRes.errMsg == 'login:ok') {
+ const res = await third.wechat.login({
+ platform: 'openPlatform',
+ shareInfo: uni.getStorageSync('shareLog') || {},
+ payload: encodeURIComponent(
+ JSON.stringify({
+ code: loginRes.code,
+ }),
+ ),
+ });
+
+ if (res.error === 0) {
+ resolve(true);
+ }
+ } else {
+ uni.showToast({
+ icon: 'none',
+ title: loginRes.errMsg,
+ });
+ }
+ resolve(false);
+ });
+};
+
+export default {
+ load,
+ login,
+};
diff --git a/sheep/platform/share.js b/sheep/platform/share.js
new file mode 100644
index 00000000..d6d96073
--- /dev/null
+++ b/sheep/platform/share.js
@@ -0,0 +1,182 @@
+import $store from '@/sheep/store';
+import $platform from '@/sheep/platform';
+import $router from '@/sheep/router';
+import $url from '@/sheep/url';
+// #ifdef H5
+import $wxsdk from '@/sheep/libs/sdk-h5-weixin';
+// #endif
+
+// 设置分享的平台渠道: 1=H5,2=微信公众号网页,3=微信小程序,4=App,...按需扩展
+const platformMap = ['H5', 'WechatOfficialAccount', 'WechatMiniProgram', 'App'];
+
+// 设置分享方式: 1=直接转发,2=海报,3=复制链接,...按需扩展
+const fromMap = ['forward', 'poster', 'link'];
+
+// 设置分享信息参数
+const getShareInfo = (
+ scene = {
+ title: '', // 自定义分享标题
+ desc: '', // 自定义描述
+ image: '', // 自定义分享图片
+ params: {}, // 自定义分享参数
+ },
+ poster = {
+ // 自定义海报数据
+ type: 'user',
+ },
+) => {
+ let shareInfo = {
+ title: '', // 分享标题
+ desc: '', // 描述
+ image: '', // 分享图片
+ path: '', // 分享页面+参数
+ link: '', // 分享Url+参数
+ query: '', // 分享参数
+ poster, // 海报所需数据
+ };
+
+ const app = $store('app');
+ const shareConfig = app.platform.share;
+
+ // 自动拼接分享用户参数
+ const query = buildSpmQuery(scene.params);
+ shareInfo.query = query;
+
+ // 配置分享链接地址
+ shareInfo.link = buildSpmLink(query, shareConfig.linkAddress);
+
+ // 配置转发参数
+ if (shareConfig.methods.includes('forward')) {
+ if (shareConfig.forwardInfo.title === '' || shareConfig.forwardInfo.image === '') {
+ console.log('请在平台设置中配置转发信息');
+ }
+ // 设置自定义分享信息
+ shareInfo.title = scene.title || shareConfig.forwardInfo.title;
+ shareInfo.image = $url.cdn(scene.image || shareConfig.forwardInfo.image);
+ shareInfo.desc = scene.desc || shareConfig.forwardInfo.subtitle;
+ shareInfo.path = buildSpmPath(query);
+ }
+
+ return shareInfo;
+};
+
+// 构造spm分享参数
+const buildSpmQuery = (params) => {
+ const user = $store('user');
+ let shareId = '0'; // 设置分享者用户ID
+ if (typeof params.shareId === 'undefined') {
+ if (user.isLogin) {
+ shareId = user.userInfo.id;
+ }
+ }
+ let page = '1'; // 页面类型: 1=首页(默认),2=商品,3=拼团商品,4=秒杀商品,5=邀请参团...按需扩展
+ if (typeof params.page !== 'undefined') {
+ page = params.page;
+ }
+ let query = '0'; // 设置页面ID: 如商品ID、拼团ID等
+ if (typeof params.query !== 'undefined') {
+ query = params.query;
+ }
+ let platform = platformMap.indexOf($platform.name) + 1;
+ let from = '1';
+ if (typeof params.from !== 'undefined') {
+ from = platformMap.indexOf(params.from) + 1;
+ }
+ //spmParams = ... 可按需扩展
+ return `spm=${shareId}.${page}.${query}.${platform}.${from}`;
+};
+
+// 构造页面分享参数
+const buildSpmPath = (query) => {
+ return `/pages/index/index?${query}`;
+};
+
+// 构造分享链接
+const buildSpmLink = (query, linkAddress = '') => {
+ return `${linkAddress}?${query}`;
+};
+
+// 解析Spm
+const decryptSpm = (spm) => {
+ const user = $store('user');
+ let shareParamsArray = spm.split('.');
+ let shareParams = {
+ spm,
+ shareId: 0,
+ page: '',
+ query: {},
+ platform: '',
+ from: '',
+ };
+ let query;
+ shareParams.shareId = shareParamsArray[0];
+ switch (shareParamsArray[1]) {
+ case '1':
+ // 默认首页不跳转
+ shareParams.page = '/pages/index/index';
+ break;
+ case '2':
+ // 普通商品
+ shareParams.page = '/pages/goods/index';
+ shareParams.query = {
+ id: shareParamsArray[2],
+ };
+ break;
+ case '3':
+ // 拼团商品
+ shareParams.page = '/pages/goods/groupon';
+ query = shareParamsArray[2].split(',');
+ shareParams.query = {
+ id: query[0],
+ activity_id: query[1],
+ };
+ break;
+ case '4':
+ // 秒杀商品
+ shareParams.page = '/pages/goods/seckill';
+ query = shareParamsArray[2].split(',');
+ shareParams.query = {
+ id: query[0],
+ activity_id: query[1],
+ };
+ break;
+ case '5':
+ // 参与拼团
+ shareParams.page = '/pages/activity/groupon/detail';
+ shareParams.query = {
+ id: shareParamsArray[2],
+ };
+ break;
+ }
+ shareParams.platform = platformMap[shareParamsArray[3] - 1];
+ shareParams.from = fromMap[shareParamsArray[4] - 1];
+ if (shareParams.shareId != 0) {
+ // 已登录 立即添加分享记录
+ if (user.isLogin) {
+ user.addShareLog(shareParams);
+ } else {
+ // 未登录 待用户登录后添加分享记录
+ uni.setStorageSync('shareLog', shareParams);
+ }
+ }
+
+ if (shareParams.page !== '/pages/index/index') {
+ $router.go(shareParams.page, shareParams.query);
+ }
+ return shareParams;
+};
+
+// 更新公众号分享sdk
+const updateShareInfo = (shareInfo) => {
+ // #ifdef H5
+ if ($platform.name === 'WechatOfficialAccount') {
+ $wxsdk.updateShareInfo(shareInfo);
+ }
+ // #endif
+};
+
+export default {
+ getShareInfo,
+ updateShareInfo,
+ decryptSpm,
+};
diff --git a/sheep/request/index.js b/sheep/request/index.js
new file mode 100644
index 00000000..08abed1d
--- /dev/null
+++ b/sheep/request/index.js
@@ -0,0 +1,205 @@
+/**
+ * Shopro-request
+ * @description api模块管理,loading配置,请求拦截,错误处理
+ */
+
+import Request from '@/sheep/libs/luch-request/core/Request';
+import { baseUrl, apiPath } from '@/sheep/config';
+import $store from '@/sheep/store';
+import $platform from '@/sheep/platform';
+import { showAuthModal } from '@/sheep/hooks/useModal';
+
+const options = {
+ // 显示操作成功消息 默认不显示
+ showSuccess: false,
+ // 成功提醒 默认使用后端返回值
+ successMsg: '',
+ // 显示失败消息 默认显示
+ showError: true,
+ // 失败提醒 默认使用后端返回信息
+ errorMsg: '',
+ // 显示请求时loading模态框 默认显示
+ showLoading: true,
+ // loading提醒文字
+ loadingMsg: '加载中',
+ // 需要授权才能请求 默认放开
+ auth: false,
+ // ...
+};
+
+// Loading全局实例
+let LoadingInstance = {
+ target: null,
+ count: 0,
+};
+
+/**
+ * 关闭loading
+ */
+function closeLoading() {
+ if (LoadingInstance.count > 0) LoadingInstance.count--;
+ if (LoadingInstance.count === 0) uni.hideLoading();
+}
+
+/**
+ * @description 请求基础配置 可直接使用访问自定义请求
+ */
+const http = new Request({
+ baseURL: baseUrl,
+ timeout: 8000,
+ method: 'GET',
+ header: {
+ Accept: 'text/json',
+ 'Content-Type': 'application/json;charset=UTF-8',
+ platform: $platform.name,
+ },
+ // #ifdef APP-PLUS
+ sslVerify: false,
+ // #endif
+ // #ifdef H5
+ // 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+)
+ withCredentials: false,
+ // #endif
+ custom: options,
+});
+
+/**
+ * @description 请求拦截器
+ */
+http.interceptors.request.use(
+ (config) => {
+ if (config.custom.auth && !$store('user').isLogin) {
+ showAuthModal();
+ return Promise.reject();
+ }
+ if (config.custom.showLoading) {
+ LoadingInstance.count++;
+ LoadingInstance.count === 1 &&
+ uni.showLoading({
+ title: config.custom.loadingMsg,
+ mask: true,
+ fail: () => {
+ uni.hideLoading();
+ },
+ });
+ }
+ const token = uni.getStorageSync('token');
+ if (token) config.header['Authorization'] = token;
+ return config;
+ },
+ (error) => {
+ return Promise.reject(error);
+ },
+);
+
+/**
+ * @description 响应拦截器
+ */
+http.interceptors.response.use(
+ (response) => {
+ // 自动设置登陆令牌
+ if (response.header.authorization || response.header.Authorization) {
+ $store('user').setToken(response.header.authorization || response.header.Authorization);
+ }
+
+ response.config.custom.showLoading && closeLoading();
+ if (response.data.error !== 0) {
+ if (response.config.custom.showError)
+ uni.showToast({
+ title: response.data.msg || '服务器开小差啦,请稍后再试~',
+ icon: 'none',
+ mask: true,
+ });
+ return Promise.resolve(response.data);
+ }
+ if (
+ response.data.error === 0 &&
+ response.data.msg !== '' &&
+ response.config.custom.showSuccess
+ ) {
+ uni.showToast({
+ title: response.config.custom.successMsg || response.data.msg,
+ icon: 'none',
+ });
+ }
+ return Promise.resolve(response.data);
+ },
+ (error) => {
+ const userStore = $store('user');
+ const isLogin = userStore.isLogin;
+ let errorMessage = '网络请求出错';
+ if (error !== undefined) {
+ switch (error.statusCode) {
+ case 400:
+ errorMessage = '请求错误';
+ break;
+ case 401:
+ if (isLogin) {
+ errorMessage = '您的登陆已过期';
+ } else {
+ errorMessage = '请先登录';
+ }
+ userStore.logout(true);
+ showAuthModal();
+ break;
+ case 403:
+ errorMessage = '拒绝访问';
+ break;
+ case 404:
+ errorMessage = '请求出错';
+ break;
+ case 408:
+ errorMessage = '请求超时';
+ break;
+ case 429:
+ errorMessage = '请求频繁, 请稍后再访问';
+ break;
+ case 500:
+ errorMessage = '服务器开小差啦,请稍后再试~';
+ break;
+ case 501:
+ errorMessage = '服务未实现';
+ break;
+ case 502:
+ errorMessage = '网络错误';
+ break;
+ case 503:
+ errorMessage = '服务不可用';
+ break;
+ case 504:
+ errorMessage = '网络超时';
+ break;
+ case 505:
+ errorMessage = 'HTTP版本不受支持';
+ break;
+ }
+ if (error.errMsg.includes('timeout')) errorMessage = '请求超时';
+ // #ifdef H5
+ if (error.errMsg.includes('Network'))
+ errorMessage = window.navigator.onLine ? '服务器异常' : '请检查您的网络连接';
+ // #endif
+ }
+
+ if (error && error.config) {
+ if (error.config.custom.showError === false) {
+ uni.showToast({
+ title: error.data?.msg || errorMessage,
+ icon: 'none',
+ mask: true,
+ });
+ }
+ error.config.custom.showLoading && closeLoading();
+ }
+
+ return false;
+ },
+);
+
+const request = (config) => {
+ if (config.url[0] !== '/') {
+ config.url = apiPath + config.url;
+ }
+ return http.middleware(config);
+};
+
+export default request;
diff --git a/sheep/router/index.js b/sheep/router/index.js
new file mode 100644
index 00000000..22a43d99
--- /dev/null
+++ b/sheep/router/index.js
@@ -0,0 +1,192 @@
+import $store from '@/sheep/store';
+import { showAuthModal, showShareModal } from '@/sheep/hooks/useModal';
+import { isNumber, isString, isEmpty, startsWith, isObject, isNil, clone } from 'lodash';
+import throttle from '@/sheep/helper/throttle';
+
+const _go = (
+ path,
+ params = {},
+ options = {
+ redirect: false,
+ },
+) => {
+ let page = ''; // 跳转页面
+ let query = ''; // 页面参数
+ let url = ''; // 跳转页面完整路径
+
+ if (isString(path)) {
+ // 判断跳转类型是 path | 还是http
+ if (startsWith(path, 'http')) {
+ // #ifdef H5
+ window.location = path;
+ return;
+ // #endif
+ // #ifndef H5
+ page = `/pages/public/webview`;
+ query = `url=${encodeURIComponent(path)}`;
+ // #endif
+ } else if (startsWith(path, 'action:')) {
+ handleAction(path);
+ return;
+ } else {
+ [page, query] = path.split('?');
+ }
+ if (!isEmpty(params)) {
+ let query2 = paramsToQuery(params);
+ if (isEmpty(query)) {
+ query = query2;
+ } else {
+ query += '&' + query2;
+ }
+ }
+ }
+
+ if (isObject(path)) {
+ page = path.url;
+ if (!isNil(path.params)) {
+ query = paramsToQuery(path.params);
+ }
+ }
+
+ const nextRoute = ROUTES_MAP[page];
+
+ // 未找到指定跳转页面
+ // mark: 跳转404页
+ if (!nextRoute) {
+ console.log(`%c跳转路径参数错误<${page || 'EMPTY'}>`, 'color:red;background:yellow');
+ return;
+ }
+
+ // 页面登录拦截
+ if (nextRoute.meta?.auth && !$store('user').isLogin) {
+ showAuthModal();
+ return;
+ }
+
+ url = page;
+ if (!isEmpty(query)) {
+ url += `?${query}`;
+ }
+
+ // 跳转底部导航
+ if (TABBAR.includes(page)) {
+ uni.switchTab({
+ url,
+ });
+ return;
+ }
+
+ // 使用redirect跳转
+ if (options.redirect) {
+ uni.redirectTo({
+ url,
+ });
+ return;
+ }
+
+ uni.navigateTo({
+ url,
+ });
+};
+
+// 限流 防止重复点击跳转
+function go(...args) {
+ throttle(() => {
+ _go(...args);
+ });
+}
+
+function paramsToQuery(params) {
+ if (isEmpty(params)) {
+ return '';
+ }
+ // return new URLSearchParams(Object.entries(params)).toString();
+ let query = [];
+ for (let key in params) {
+ query.push(key + '=' + params[key]);
+ }
+
+ return query.join('&');
+}
+
+function back() {
+ // #ifdef H5
+ history.back();
+ // #endif
+
+ // #ifndef H5
+ uni.navigateBack();
+ // #endif
+}
+
+function redirect(path, params = {}) {
+ go(path, params, {
+ redirect: true,
+ });
+}
+
+// 检测是否有浏览器历史
+function hasHistory() {
+ // #ifndef H5
+ const pages = getCurrentPages();
+ if (pages.length > 1) {
+ return true;
+ }
+ return false;
+ // #endif
+
+ // #ifdef H5
+ return !!history.state.back;
+ // #endif
+}
+
+function getCurrentRoute(field = '') {
+ let currentPage = getCurrentPage();
+ // #ifdef MP
+ currentPage.$page['route'] = currentPage.route;
+ currentPage.$page['options'] = currentPage.options;
+ // #endif
+ if (field !== '') {
+ return currentPage.$page[field];
+ } else {
+ return currentPage.$page;
+ }
+}
+
+function getCurrentPage() {
+ let pages = getCurrentPages();
+ return pages[pages.length - 1];
+}
+
+function getCur() {
+ let a = getCurrentPages();
+ let pages = clone(a);
+ return pages;
+}
+
+function handleAction(path) {
+ const action = path.split(':');
+ switch (action[1]) {
+ case 'showShareModal':
+ showShareModal();
+ break;
+ }
+}
+
+function error(errCode, errMsg = '') {
+ redirect('/pages/public/error', {
+ errCode,
+ errMsg,
+ });
+}
+
+export default {
+ go,
+ back,
+ hasHistory,
+ redirect,
+ getCurrentPage,
+ getCurrentRoute,
+ getCur,
+ error,
+};
diff --git a/sheep/router/utils/strip-json-comments.js b/sheep/router/utils/strip-json-comments.js
new file mode 100644
index 00000000..59959922
--- /dev/null
+++ b/sheep/router/utils/strip-json-comments.js
@@ -0,0 +1,79 @@
+const singleComment = Symbol('singleComment');
+const multiComment = Symbol('multiComment');
+
+const stripWithoutWhitespace = () => '';
+const stripWithWhitespace = (string, start, end) => string.slice(start, end).replace(/\S/g, ' ');
+
+const isEscaped = (jsonString, quotePosition) => {
+ let index = quotePosition - 1;
+ let backslashCount = 0;
+
+ while (jsonString[index] === '\\') {
+ index -= 1;
+ backslashCount += 1;
+ }
+
+ return Boolean(backslashCount % 2);
+};
+
+export default function stripJsonComments(jsonString, { whitespace = true } = {}) {
+ if (typeof jsonString !== 'string') {
+ throw new TypeError(
+ `Expected argument \`jsonString\` to be a \`string\`, got \`${typeof jsonString}\``,
+ );
+ }
+
+ const strip = whitespace ? stripWithWhitespace : stripWithoutWhitespace;
+
+ let isInsideString = false;
+ let isInsideComment = false;
+ let offset = 0;
+ let result = '';
+
+ for (let index = 0; index < jsonString.length; index++) {
+ const currentCharacter = jsonString[index];
+ const nextCharacter = jsonString[index + 1];
+
+ if (!isInsideComment && currentCharacter === '"') {
+ const escaped = isEscaped(jsonString, index);
+ if (!escaped) {
+ isInsideString = !isInsideString;
+ }
+ }
+
+ if (isInsideString) {
+ continue;
+ }
+
+ if (!isInsideComment && currentCharacter + nextCharacter === '//') {
+ result += jsonString.slice(offset, index);
+ offset = index;
+ isInsideComment = singleComment;
+ index++;
+ } else if (isInsideComment === singleComment && currentCharacter + nextCharacter === '\r\n') {
+ index++;
+ isInsideComment = false;
+ result += strip(jsonString, offset, index);
+ offset = index;
+ continue;
+ } else if (isInsideComment === singleComment && currentCharacter === '\n') {
+ isInsideComment = false;
+ result += strip(jsonString, offset, index);
+ offset = index;
+ } else if (!isInsideComment && currentCharacter + nextCharacter === '/*') {
+ result += jsonString.slice(offset, index);
+ offset = index;
+ isInsideComment = multiComment;
+ index++;
+ continue;
+ } else if (isInsideComment === multiComment && currentCharacter + nextCharacter === '*/') {
+ index++;
+ isInsideComment = false;
+ result += strip(jsonString, offset, index + 1);
+ offset = index + 1;
+ continue;
+ }
+ }
+
+ return result + (isInsideComment ? strip(jsonString.slice(offset)) : jsonString.slice(offset));
+}
diff --git a/sheep/router/utils/uni-read-pages-v3.js b/sheep/router/utils/uni-read-pages-v3.js
new file mode 100644
index 00000000..303f10a5
--- /dev/null
+++ b/sheep/router/utils/uni-read-pages-v3.js
@@ -0,0 +1,103 @@
+'use strict';
+Object.defineProperty(exports, '__esModule', {
+ value: true,
+});
+const fs = require('fs');
+import stripJsonComments from './strip-json-comments';
+import { isArray, isEmpty } from 'lodash';
+
+class TransformPages {
+ constructor({ includes, pagesJsonDir }) {
+ this.includes = includes;
+ this.uniPagesJSON = JSON.parse(stripJsonComments(fs.readFileSync(pagesJsonDir, 'utf-8')));
+ this.routes = this.getPagesRoutes().concat(this.getSubPackagesRoutes());
+ this.tabbar = this.getTabbarRoutes();
+ this.routesMap = this.transformPathToKey(this.routes);
+ }
+ /**
+ * 通过读取pages.json文件 生成直接可用的routes
+ */
+ getPagesRoutes(pages = this.uniPagesJSON.pages, rootPath = null) {
+ let routes = [];
+ for (let i = 0; i < pages.length; i++) {
+ const item = pages[i];
+ let route = {};
+ for (let j = 0; j < this.includes.length; j++) {
+ const key = this.includes[j];
+ let value = item[key];
+ if (key === 'path') {
+ value = rootPath ? `/${rootPath}/${value}` : `/${value}`;
+ }
+ if (key === 'aliasPath' && i == 0 && rootPath == null) {
+ route[key] = route[key] || '/';
+ } else if (value !== undefined) {
+ route[key] = value;
+ }
+ }
+ routes.push(route);
+ }
+ return routes;
+ }
+ /**
+ * 解析小程序分包路径
+ */
+ getSubPackagesRoutes() {
+ if (!(this.uniPagesJSON && this.uniPagesJSON.subPackages)) {
+ return [];
+ }
+ const subPackages = this.uniPagesJSON.subPackages;
+ let routes = [];
+ for (let i = 0; i < subPackages.length; i++) {
+ const subPages = subPackages[i].pages;
+ const root = subPackages[i].root;
+ const subRoutes = this.getPagesRoutes(subPages, root);
+ routes = routes.concat(subRoutes);
+ }
+ return routes;
+ }
+
+ getTabbarRoutes() {
+ if (!(this.uniPagesJSON && this.uniPagesJSON.tabBar && this.uniPagesJSON.tabBar.list)) {
+ return [];
+ }
+ const tabbar = this.uniPagesJSON.tabBar.list;
+ let tabbarMap = [];
+ tabbar.forEach((bar) => {
+ tabbarMap.push('/' + bar.pagePath);
+ });
+ return tabbarMap;
+ }
+
+ transformPathToKey(list) {
+ if (!isArray(list) || isEmpty(list)) {
+ return [];
+ }
+ let map = {};
+ list.forEach((i) => {
+ map[i.path] = i;
+ });
+ return map;
+ }
+}
+
+function uniReadPagesV3Plugin({ pagesJsonDir, includes }) {
+ let defaultIncludes = ['path', 'aliasPath', 'name'];
+ includes = [...defaultIncludes, ...includes];
+ let pages = new TransformPages({
+ pagesJsonDir,
+ includes,
+ });
+ return {
+ name: 'uni-read-pages-v3',
+ config(config) {
+ return {
+ define: {
+ ROUTES: pages.routes,
+ ROUTES_MAP: pages.routesMap,
+ TABBAR: pages.tabbar,
+ },
+ };
+ },
+ };
+}
+exports.default = uniReadPagesV3Plugin;
diff --git a/sheep/scss/_main.scss b/sheep/scss/_main.scss
new file mode 100644
index 00000000..999513a9
--- /dev/null
+++ b/sheep/scss/_main.scss
@@ -0,0 +1,354 @@
+body {
+ color: var(--text-a);
+ background-color: var(--ui-BG-1) !important;
+ font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
+ sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+}
+
+/* ==================
+ 初始化
+ ==================== */
+.ui-link {
+ cursor: pointer;
+}
+navigator {
+ display: inline-flex;
+}
+navigator.navigator-hover {
+ background-color: inherit;
+ transform: translate(1rpx, 1rpx);
+ // opacity: 1;
+}
+
+/* ==================
+ 辅助类
+ ==================== */
+.none {
+ display: none !important;
+}
+.inline {
+ display: inline !important;
+}
+.inline-block {
+ display: inline-block !important;
+}
+.block {
+ display: block !important;
+}
+.touch-none {
+ pointer-events: none;
+}
+.touch-all {
+ pointer-events: all;
+}
+.flex {
+ display: flex !important;
+}
+.inline-flex {
+ display: inline-flex !important;
+}
+.w-100 {
+ width: 100%;
+}
+/* -- 浮动 -- */
+.cf::after,
+.cf::before {
+ content: '';
+ display: table;
+}
+.cf::after {
+ clear: both;
+}
+.fl {
+ float: left;
+}
+.fr {
+ float: right;
+}
+.position-center {
+ @include position-center;
+}
+.position-relative {
+ position: relative;
+}
+/* -- 工具类 -- */
+@function negativify-map($map) {
+ $result: ();
+ @each $key, $value in $map {
+ @if $key != 0 {
+ $result: map-merge($result, ('n' + $key: (-$value)));
+ }
+ }
+ @return $result;
+}
+
+$utilities: () !default;
+$utilities: map-merge(
+ (
+ 'margin': (
+ responsive: true,
+ property: margin,
+ class: m,
+ values:
+ map-merge(
+ $spacers,
+ (
+ auto: auto,
+ )
+ ),
+ ),
+ 'margin-x': (
+ property: margin-right margin-left,
+ class: mx,
+ values:
+ map-merge(
+ $spacers,
+ (
+ auto: auto,
+ )
+ ),
+ ),
+ 'margin-y': (
+ property: margin-top margin-bottom,
+ class: my,
+ values:
+ map-merge(
+ $spacers,
+ (
+ auto: auto,
+ )
+ ),
+ ),
+ 'margin-top': (
+ property: margin-top,
+ class: mt,
+ values:
+ map-merge(
+ $spacers,
+ (
+ auto: auto,
+ )
+ ),
+ ),
+ 'margin-right': (
+ property: margin-right,
+ class: mr,
+ values:
+ map-merge(
+ $spacers,
+ (
+ auto: auto,
+ )
+ ),
+ ),
+ 'margin-bottom': (
+ property: margin-bottom,
+ class: mb,
+ values:
+ map-merge(
+ $spacers,
+ (
+ auto: auto,
+ )
+ ),
+ ),
+ 'margin-left': (
+ property: margin-left,
+ class: ml,
+ values:
+ map-merge(
+ $spacers,
+ (
+ auto: auto,
+ )
+ ),
+ ),
+ 'padding': (
+ responsive: true,
+ property: padding,
+ class: p,
+ values: $spacers,
+ ),
+ 'padding-x': (
+ property: padding-right padding-left,
+ class: px,
+ values: $spacers,
+ ),
+ 'padding-y': (
+ property: padding-top padding-bottom,
+ class: py,
+ values: $spacers,
+ ),
+ 'padding-top': (
+ property: padding-top,
+ class: pt,
+ values: $spacers,
+ ),
+ 'padding-right': (
+ property: padding-right,
+ class: pr,
+ values: $spacers,
+ ),
+ 'padding-bottom': (
+ property: padding-bottom,
+ class: pb,
+ values: $spacers,
+ ),
+ 'padding-left': (
+ property: padding-left,
+ class: pl,
+ values: $spacers,
+ ),
+ 'font-weight': (
+ property: font-weight,
+ class: text,
+ values: (
+ light: $font-weight-light,
+ lighter: $font-weight-lighter,
+ normal: $font-weight-normal,
+ bold: $font-weight-bold,
+ bolder: $font-weight-bolder,
+ ),
+ ),
+ 'text-align': (
+ property: text-align,
+ class: text,
+ values: left right center,
+ ),
+ 'font-color': (
+ property: color,
+ class: text,
+ values:
+ map-merge(
+ $colors,
+ map-merge(
+ $grays,
+ map-merge(
+ $darks,
+ (
+ 'reset': inherit,
+ )
+ )
+ )
+ ),
+ ),
+ 'line-height': (
+ property: line-height,
+ class: lh,
+ values: (
+ 1: 1,
+ sm: $line-height-sm,
+ base: $line-height-base,
+ lg: $line-height-lg,
+ ),
+ ),
+ 'white-space': (
+ property: white-space,
+ class: text,
+ values: (
+ nowrap: nowrap,
+ ),
+ ),
+ 'radius': (
+ property: border-radius,
+ class: radius,
+ values: (
+ null: $radius,
+ sm: $radius-sm,
+ lg: $radius-lg,
+ 0: 0,
+ ),
+ ),
+ 'round': (
+ property: border-radius,
+ class: round,
+ values: (
+ null: $round-pill,
+ circle: 50%,
+ ),
+ ),
+ 'radius-top': (
+ property: border-top-left-radius border-top-right-radius,
+ class: radius-top,
+ values: (
+ null: $radius,
+ ),
+ ),
+ 'radius-right': (
+ property: border-top-right-radius border-bottom-right-radius,
+ class: radius-right,
+ values: (
+ null: $radius,
+ ),
+ ),
+ 'radius-bottom': (
+ property: border-bottom-right-radius border-bottom-left-radius,
+ class: radius-bottom,
+ values: (
+ null: $radius,
+ ),
+ ),
+ 'radius-left': (
+ property: border-bottom-left-radius border-top-left-radius,
+ class: radius-left,
+ values: (
+ null: $radius,
+ ),
+ ),
+ 'radius-lr': (
+ property: border-top-left-radius border-bottom-right-radius,
+ class: radius-lr,
+ values: (
+ null: $radius,
+ ),
+ ),
+ 'radius-lrs': (
+ property: border-top-right-radius border-bottom-left-radius,
+ class: radius-lr,
+ values: (
+ null: 0,
+ ),
+ ),
+ 'radius-rl': (
+ property: border-top-right-radius border-bottom-left-radius,
+ class: radius-rl,
+ values: (
+ null: $radius,
+ ),
+ ),
+ 'radius-rls': (
+ property: border-top-left-radius border-bottom-right-radius,
+ class: radius-rl,
+ values: (
+ null: 0,
+ ),
+ ),
+ ),
+ $utilities
+);
+@each $key, $utility in $utilities {
+ @if type-of($utility) == 'map' {
+ $values: map-get($utility, values);
+ @if type-of($values) == 'string' or type-of(nth($values, 1)) != 'list' {
+ $values: zip($values, $values);
+ }
+ @each $key, $value in $values {
+ $properties: map-get($utility, property);
+ @if type-of($properties) == 'string' {
+ $properties: append((), $properties);
+ }
+ $property-class: if(
+ map-has-key($utility, class),
+ map-get($utility, class),
+ nth($properties, 1)
+ );
+ $property-class: if($property-class == null, '', $property-class);
+ $property-class-modifier: if($key, if($property-class == '', '', '-') + $key, '');
+ .#{$property-class + $property-class-modifier} {
+ @each $property in $properties {
+ #{$property}: $value !important;
+ }
+ }
+ }
+ }
+}
diff --git a/sheep/scss/_mixins.scss b/sheep/scss/_mixins.scss
new file mode 100644
index 00000000..299f7b16
--- /dev/null
+++ b/sheep/scss/_mixins.scss
@@ -0,0 +1,61 @@
+@mixin bg-square {
+ background: {
+ color: #fff;
+ image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%),
+ linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%);
+ size: 40rpx 40rpx;
+ position: 0 0, 20rpx 20rpx;
+ }
+}
+
+@mixin flex($direction: row) {
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ flex-direction: $direction;
+}
+@mixin flex-bar {
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+@mixin flex-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+@mixin arrow {
+ content: '';
+ height: 0;
+ width: 0;
+ position: absolute;
+}
+@mixin arrow-top {
+ @include arrow;
+ // border-color: transparent transparent $ui-BG;
+ border-style: none solid solid;
+ border-width: 0 20rpx 20rpx;
+}
+
+@mixin arrow-right {
+ @include arrow;
+ // border-color: transparent $ui-BG transparent;
+ border-style: solid solid solid none;
+ border-width: 20rpx 20rpx 20rpx 0;
+}
+@mixin position-center {
+ position: absolute !important;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ margin: auto;
+}
+
+@mixin blur {
+ -webkit-backdrop-filter: blur(20px);
+ backdrop-filter: blur(20px);
+ color: var(--ui-TC);
+}
diff --git a/sheep/scss/_tools.scss b/sheep/scss/_tools.scss
new file mode 100644
index 00000000..e1fb636e
--- /dev/null
+++ b/sheep/scss/_tools.scss
@@ -0,0 +1,286 @@
+/* ==================
+ 常用工具
+ ==================== */
+
+.ss-bg-opactity-block {
+ background-color: rgba(#000, 0.2);
+ color: #fff;
+}
+
+/* ==================
+ flex布局
+ ==================== */
+
+.ss-flex {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+.ss-flex-1 {
+ flex: 1;
+}
+
+.ss-flex-col {
+ display: flex;
+ flex-direction: column;
+}
+
+.ss-flex-wrap {
+ flex-wrap: wrap;
+}
+
+.ss-flex-nowrap {
+ flex-wrap: nowrap;
+}
+
+.ss-col-center {
+ align-items: center;
+}
+
+.ss-col-top {
+ align-items: flex-start;
+}
+
+.ss-col-bottom {
+ align-items: flex-end;
+}
+
+.ss-col-stretch {
+ align-items: stretch;
+}
+
+.ss-row-center {
+ justify-content: center;
+}
+
+.ss-row-left {
+ justify-content: flex-start;
+}
+
+.ss-row-right {
+ justify-content: flex-end;
+}
+
+.ss-row-between {
+ justify-content: space-between;
+}
+
+.ss-row-around {
+ justify-content: space-around;
+}
+
+.ss-self-start {
+ align-self: flex-start;
+}
+
+.ss-self-end {
+ align-self: flex-end;
+}
+
+.ss-self-center {
+ align-self: center;
+}
+.ss-h-100 {
+ height: 100%;
+}
+.ss-w-100 {
+ width: 100%;
+}
+
+/* ==================
+
+ margin padding: 内外边距
+
+ ==================== */
+@for $i from 0 through 100 {
+ // 只要双数和能被5除尽的数
+ @if $i % 2==0 or $i % 5==0 {
+ // 得出:u-margin-30或者u-m-30
+ .ss-margin-#{$i},
+ .ss-m-#{$i} {
+ margin: $i + rpx;
+ }
+ .ss-m-x-#{$i} {
+ margin-left: $i + rpx;
+ margin-right: $i + rpx;
+ }
+ .ss-m-y-#{$i} {
+ margin-top: $i + rpx;
+ margin-bottom: $i + rpx;
+ }
+
+ // 得出:u-padding-30或者u-p-30
+ .ss-padding-#{$i},
+ .ss-p-#{$i} {
+ padding: $i + rpx;
+ }
+ .ss-p-x-#{$i} {
+ padding-left: $i + rpx;
+ padding-right: $i + rpx;
+ }
+ .ss-p-y-#{$i} {
+ padding-top: $i + rpx;
+ padding-bottom: $i + rpx;
+ }
+
+ @each $short, $long in l left, t top, r right, b bottom {
+ // 缩写版,结果如: u-m-l-30
+ // 定义外边距
+ .ss-m-#{$short}-#{$i} {
+ margin-#{$long}: $i + rpx;
+ }
+
+ // 定义内边距
+ .ss-p-#{$short}-#{$i} {
+ padding-#{$long}: $i + rpx;
+ }
+
+ // 完整版,结果如:u-margin-left-30
+ // 定义外边距
+ .ss-margin-#{$long}-#{$i} {
+ margin-#{$long}: $i + rpx;
+ }
+
+ // 定义内边距
+ .ss-padding-#{$long}-#{$i} {
+ padding-#{$long}: $i + rpx;
+ }
+ }
+ }
+}
+
+/* ==================
+
+ radius
+
+ ==================== */
+@for $i from 0 through 100 {
+ // 只要双数和能被5除尽的数
+ @if $i % 2==0 or $i % 5==0 {
+ .ss-radius-#{$i},
+ .ss-r-#{$i} {
+ border-radius: $i + rpx;
+ }
+
+ .ss-r-t-#{$i} {
+ border-top-left-radius: $i + rpx;
+ border-top-right-radius: $i + rpx;
+ }
+
+ .ss-r-b-#{$i} {
+ border-bottom-left-radius: $i + rpx;
+ border-bottom-right-radius: $i + rpx;
+ }
+
+ @each $short, $long in tl 'top-left', tr 'top-right', bl 'bottom-right', br 'bottom-right' {
+ // 定义外边距
+ .ss-r-#{$short}-#{$i} {
+ border-#{$long}-radius: $i + rpx;
+ }
+
+ // 定义内边距
+ .ss-radius-#{$long}-#{$i} {
+ border-#{$long}-radius: $i + rpx;
+ }
+ }
+ }
+}
+
+/* ==================
+
+ 溢出省略号
+ @param {Number} 行数
+
+ ==================== */
+@mixin ellipsis($rowCount: 1) {
+ // @if $rowCount <=1 {
+ // overflow: hidden;
+ // text-overflow: ellipsis;
+ // white-space: nowrap;
+ // } @else {
+ // min-width: 0;
+ // overflow: hidden;
+ // text-overflow: ellipsis;
+ // display: -webkit-box;
+ // -webkit-line-clamp: $rowCount;
+ // -webkit-box-orient: vertical;
+ // }
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: $rowCount;
+ -webkit-box-orient: vertical;
+}
+
+@for $i from 1 through 6 {
+ .ss-line-#{$i} {
+ @include ellipsis($i);
+ }
+}
+
+/* ==================
+ hover
+ ==================== */
+.ss-hover-class {
+ background-color: $gray-c;
+ opacity: 0.6;
+}
+.ss-hover-btn {
+ transform: translate(1px, 1px);
+}
+
+/* ==================
+ 底部安全区域
+ ==================== */
+
+.ss-safe-bottom {
+ padding-bottom: 0;
+ padding-bottom: calc(constant(safe-area-inset-bottom) / 5 * 3);
+ padding-bottom: calc(env(safe-area-inset-bottom) / 5 * 3);
+}
+
+/* ==================
+
+ 字体大小
+
+ ==================== */
+
+@for $i from 20 through 50 {
+ .ss-font-#{$i} {
+ font-size: $i + rpx;
+ }
+}
+
+/* ==================
+ 按钮
+ ==================== */
+.ss-reset-button {
+ padding: 0;
+ margin: 0;
+ font-size: inherit;
+ background-color: transparent;
+ color: inherit;
+ position: relative;
+ border: 0rpx;
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ text-align: center;
+ text-decoration: none;
+ white-space: nowrap;
+ vertical-align: baseline;
+ transform: translate(0, 0);
+}
+.ss-reset-button.button-hover {
+ transform: translate(1px, 1px);
+ background: none;
+}
+
+.ss-reset-button::after {
+ border: none;
+}
diff --git a/sheep/scss/_var.scss b/sheep/scss/_var.scss
new file mode 100644
index 00000000..b0d88c82
--- /dev/null
+++ b/sheep/scss/_var.scss
@@ -0,0 +1,162 @@
+@import './mixins';
+
+//颜色 ,渐变背景60%
+$yellow: #ffc300; //ss-黄
+$orange: #ff6000; //ss-橘
+$red: #ff3000; //ss-红
+$pink: #e03997;
+$mauve: #b745cb;
+$purple: #652abf; //rgba(101, 42, 191, 1); // ss-紫
+$blue: #0081ff;
+$cyan: #37c0fe;
+$green: #2aae67; //ss-绿
+$olive: #8dc63f;
+$grey: #8799a3;
+$brown: #a5673f;
+$black: #484848; //ss-黑
+$golden: #e9b461; //ss-金
+
+$colors: ();
+$colors: map-merge(
+ (
+ 'yellow': $yellow,
+ 'orange': $orange,
+ 'red': $red,
+ 'pink': $pink,
+ 'mauve': $mauve,
+ 'purple': $purple,
+ 'violet': $purple,
+ 'blue': $blue,
+ 'cyan': $cyan,
+ 'green': $green,
+ 'olive': $olive,
+ 'grey': $grey,
+ 'brown': $brown,
+ 'black': $black,
+ 'golden': $golden,
+ ),
+ $colors
+);
+
+//灰度
+$bg-page: #f6f6f6;
+$white: #ffffff;
+$gray-f: #f8f9fa;
+$gray-e: #eeeeee;
+$gray-d: #dddddd;
+$gray-c: #cccccc;
+$gray-b: #bbbbbb;
+$gray-a: #aaaaaa;
+$dark-9: #999999;
+$dark-8: #888888;
+$dark-7: #777777;
+$dark-6: #666666;
+$dark-5: #555555;
+$dark-4: #484848; //ss-黑
+$dark-3: #333333;
+$dark-2: #222222;
+$dark-1: #111111;
+$black: #000000;
+
+$grays: ();
+$grays: map-merge(
+ (
+ 'white': $white,
+ 'gray-f': $gray-f,
+ 'gray-e': $gray-e,
+ 'gray-d': $gray-d,
+ 'gray-c': $gray-c,
+ 'gray-b': $gray-b,
+ 'gray-a': $gray-a,
+ 'gray': $gray-a,
+ ),
+ $grays
+);
+
+$darks: ();
+$darks: map-merge(
+ (
+ 'dark-9': $dark-9,
+ 'dark-8': $dark-8,
+ 'dark-7': $dark-7,
+ 'dark-6': $dark-6,
+ 'dark-5': $dark-5,
+ 'dark-4': $dark-4,
+ 'dark-3': $dark-3,
+ 'dark-2': $dark-2,
+ 'dark-1': $dark-1,
+ 'black': $black,
+ ),
+ $darks
+);
+
+// 边框
+$border-width: 1rpx !default; // 边框大小
+$border-color: $gray-d !default; // 边框颜色
+
+// 圆角
+$radius: 10rpx !default; // 默认圆角大小
+$radius-lg: 40rpx !default; // 大圆角
+$radius-sm: 6rpx !default; // 小圆角
+$round-pill: 1000rpx !default; // 半圆
+
+// 动画过渡
+$transition-base: all 0.2s ease-in-out !default; // 默认过渡
+$transition-base-out: all 0.04s ease-in-out !default; // 进场过渡
+$transition-fade: opacity 0.15s linear !default; // 透明过渡
+$transition-collapse: height 0.35s ease !default; // 收缩过渡
+
+// 间距
+$spacer: 20rpx !default;
+$spacers: () !default;
+$spacers: map-merge(
+ (
+ 0: 0,
+ 1: $spacer * 0.25,
+ 2: $spacer * 0.5,
+ 3: $spacer,
+ 4: $spacer * 1.5,
+ 5: $spacer * 3,
+ 6: $spacer * 5,
+ ),
+ $spacers
+);
+// 字形
+$font-weight-lighter: lighter !default;
+$font-weight-light: 300 !default;
+$font-weight-normal: 400 !default;
+$font-weight-bold: 700 !default;
+$font-weight-bolder: 900 !default;
+$fontsize: () !default;
+$fontsize: map-merge(
+ (
+ xs: 20,
+ sm: 24,
+ df: 28,
+ lg: 32,
+ xl: 36,
+ xxl: 44,
+ sl: 80,
+ xsl: 120,
+ ),
+ $fontsize
+);
+// 段落
+$line-height-base: 1.5 !default;
+$line-height-lg: 2 !default;
+$line-height-sm: 1.25 !default;
+// 图标
+$iconsize: () !default;
+$iconsize: map-merge(
+ (
+ xs: 0.5,
+ sm: 0.75,
+ df: 1,
+ lg: 1.25,
+ xl: 1.5,
+ xxl: 2,
+ sl: 6,
+ xsl: 10,
+ ),
+ $iconsize
+);
diff --git a/sheep/scss/font/OPPOSANS-M-subfont.ttf b/sheep/scss/font/OPPOSANS-M-subfont.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..88ff8353fd5fb53cf1a4d7055ba7e09a12a94514
GIT binary patch
literal 9832
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
literal 0
HcmV?d00001
diff --git a/sheep/scss/icon/_coloricon.scss b/sheep/scss/icon/_coloricon.scss
new file mode 100644
index 00000000..f391ca44
--- /dev/null
+++ b/sheep/scss/icon/_coloricon.scss
@@ -0,0 +1,1340 @@
+@font-face {
+ font-family: 'coloricon';
+ src: url('data:application/x-font-woff2;charset=utf-8;base64,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')
+ format('woff2');
+ /* #ifdef MP-ALIPAY */
+ src: url('//at.alicdn.com/t/font_1656945_d66u4pxvlq6.woff2') format('woff2'),
+ url('//at.alicdn.com/t/font_1656945_d66u4pxvlq6.woff') format('woff'),
+ url('//at.alicdn.com/t/font_1656945_d66u4pxvlq6.ttf') format('truetype');
+ /* #endif */
+ font-weight: normal;
+ font-style: normal;
+}
+[class*='cicon-'] {
+ font-family: 'coloricon';
+ display: inline-block;
+}
+
+.cicon-Aa:before {
+ content: '\e7a1';
+}
+.cicon-accounts:before {
+ content: '\e681';
+}
+.cicon-accounts-o:before {
+ content: '\e686';
+}
+.cicon-add:before {
+ content: '\e6e4';
+}
+.cicon-add-round:before {
+ content: '\e717';
+}
+.cicon-add-round-o:before {
+ content: '\e718';
+}
+.cicon-alarm:before {
+ content: '\e61e';
+}
+.cicon-album:before {
+ content: '\e759';
+}
+.cicon-alipay:before {
+ content: '\e6e1';
+}
+.cicon-android:before {
+ content: '\e6e2';
+}
+.cicon-angle:before {
+ content: '\e605';
+}
+.cicon-apple:before {
+ content: '\e8e7';
+}
+.cicon-apps:before {
+ content: '\e737';
+}
+.cicon-archive:before {
+ content: '\e7ae';
+}
+.cicon-archive-o:before {
+ content: '\e7ad';
+}
+.cicon-arrow:before {
+ content: '\e608';
+}
+.cicon-at-line:before {
+ content: '\e75c';
+}
+.cicon-avatar:before {
+ content: '\e663';
+}
+.cicon-avatar-o:before {
+ content: '\e665';
+}
+.cicon-avatars:before {
+ content: '\e67e';
+}
+.cicon-avatars-o:before {
+ content: '\e680';
+}
+.cicon-back:before {
+ content: '\e600';
+}
+.cicon-backspace:before {
+ content: '\e6a9';
+}
+.cicon-backup:before {
+ content: '\e61f';
+}
+.cicon-backup-restore:before {
+ content: '\e62d';
+}
+.cicon-barcode:before {
+ content: '\e71f';
+}
+.cicon-book:before {
+ content: '\e6a2';
+}
+.cicon-bookmark:before {
+ content: '\e6a3';
+}
+.cicon-bookmark-o:before {
+ content: '\e697';
+}
+.cicon-bookmarks:before {
+ content: '\e6a6';
+}
+.cicon-box:before {
+ content: '\e714';
+}
+.cicon-box-block:before {
+ content: '\e6ac';
+}
+.cicon-box-right:before {
+ content: '\e6a0';
+}
+.cicon-brand:before {
+ content: '\e726';
+}
+.cicon-brand-o:before {
+ content: '\e727';
+}
+.cicon-building:before {
+ content: '\e6c3';
+}
+.cicon-building-o:before {
+ content: '\e6c7';
+}
+.cicon-camera:before {
+ content: '\e6fa';
+}
+.cicon-camera-add:before {
+ content: '\e736';
+}
+.cicon-camera-add-o:before {
+ content: '\e735';
+}
+.cicon-camera-lens:before {
+ content: '\e68f';
+}
+.cicon-camera-lens-o:before {
+ content: '\e68e';
+}
+.cicon-camera-o:before {
+ content: '\e6fb';
+}
+.cicon-camera-rotate:before {
+ content: '\e71e';
+}
+.cicon-card:before {
+ content: '\e744';
+}
+.cicon-cardboard:before {
+ content: '\e7a9';
+}
+.cicon-cardboard-o:before {
+ content: '\e7aa';
+}
+.cicon-cardboard-off-o:before {
+ content: '\e7af';
+}
+.cicon-cart:before {
+ content: '\e70b';
+}
+.cicon-cart-o:before {
+ content: '\e708';
+}
+.cicon-chat:before {
+ content: '\e739';
+}
+.cicon-chat-bubble:before {
+ content: '\e69b';
+}
+.cicon-chat-bubble-o:before {
+ content: '\e6a7';
+}
+.cicon-chat-list:before {
+ content: '\e69d';
+}
+.cicon-chat-list-o:before {
+ content: '\e6aa';
+}
+.cicon-chat-o:before {
+ content: '\e73c';
+}
+.cicon-chat-smile:before {
+ content: '\e779';
+}
+.cicon-chat-smile-o:before {
+ content: '\e78e';
+}
+.cicon-chat-smiles:before {
+ content: '\e76b';
+}
+.cicon-chat-smiles-o:before {
+ content: '\e74a';
+}
+.cicon-check:before {
+ content: '\e69f';
+}
+.cicon-checkbox:before {
+ content: '\e713';
+}
+.cicon-checkbox-o:before {
+ content: '\e715';
+}
+.cicon-check-round:before {
+ content: '\e6f1';
+}
+.cicon-check-round-o:before {
+ content: '\e6f2';
+}
+.cicon-choiceness:before {
+ content: '\e728';
+}
+.cicon-choiceness-o:before {
+ content: '\e729';
+}
+.cicon-chrome:before {
+ content: '\e6e3';
+}
+.cicon-circle:before {
+ content: '\e7b0';
+}
+.cicon-circle-o:before {
+ content: '\e7b1';
+}
+.cicon-close:before {
+ content: '\e6ed';
+}
+.cicon-close-round:before {
+ content: '\e6f3';
+}
+.cicon-close-round-o:before {
+ content: '\e6f4';
+}
+.cicon-clothes:before {
+ content: '\e72a';
+}
+.cicon-clothes-o:before {
+ content: '\e72b';
+}
+.cicon-cloud:before {
+ content: '\e64e';
+}
+.cicon-cloud-done:before {
+ content: '\e641';
+}
+.cicon-cloud-download:before {
+ content: '\e647';
+}
+.cicon-cloud-o:before {
+ content: '\e646';
+}
+.cicon-cloud-off:before {
+ content: '\e64b';
+}
+.cicon-cloud-upload:before {
+ content: '\e687';
+}
+.cicon-code-box:before {
+ content: '\e7c3';
+}
+.cicon-coin:before {
+ content: '\e78a';
+}
+.cicon-coin-o:before {
+ content: '\e79d';
+}
+.cicon-comment:before {
+ content: '\e738';
+}
+.cicon-comment-o:before {
+ content: '\e70e';
+}
+.cicon-community:before {
+ content: '\e742';
+}
+.cicon-community-o:before {
+ content: '\e743';
+}
+.cicon-countdown:before {
+ content: '\e722';
+}
+.cicon-countdown-o:before {
+ content: '\e723';
+}
+.cicon-creative:before {
+ content: '\e72c';
+}
+.cicon-creative-o:before {
+ content: '\e72d';
+}
+.cicon-crop:before {
+ content: '\e6d9';
+}
+.cicon-crown:before {
+ content: '\e776';
+}
+.cicon-crown-o:before {
+ content: '\e777';
+}
+.cicon-cut:before {
+ content: '\e74b';
+}
+.cicon-DarkMode:before {
+ content: '\e7c4';
+}
+.cicon-dashboard:before {
+ content: '\e62e';
+}
+.cicon-delete:before {
+ content: '\e6bd';
+}
+.cicon-delete-close:before {
+ content: '\e6ae';
+}
+.cicon-delete-line:before {
+ content: '\e707';
+}
+.cicon-delete-line-o:before {
+ content: '\e709';
+}
+.cicon-delete-o:before {
+ content: '\e69a';
+}
+.cicon-deliver:before {
+ content: '\e7f7';
+}
+.cicon-deliver-o:before {
+ content: '\e6ff';
+}
+.cicon-demo:before {
+ content: '\e916';
+}
+.cicon-discover:before {
+ content: '\e70c';
+}
+.cicon-discover-o:before {
+ content: '\e702';
+}
+.cicon-discuss-fill:before {
+ content: '\e790';
+}
+.cicon-discuss-line:before {
+ content: '\e78f';
+}
+.cicon-dollar:before {
+ content: '\e79f';
+}
+.cicon-dollar-o:before {
+ content: '\e79e';
+}
+.cicon-done:before {
+ content: '\e633';
+}
+.cicon-done-all:before {
+ content: '\e62a';
+}
+.cicon-douyin:before {
+ content: '\e6e7';
+}
+.cicon-drop-down:before {
+ content: '\e61c';
+}
+.cicon-drop-up:before {
+ content: '\e61d';
+}
+.cicon-eject:before {
+ content: '\e63a';
+}
+.cicon-ellipse:before {
+ content: '\e74c';
+}
+.cicon-emoji:before {
+ content: '\e78d';
+}
+.cicon-emoji-o:before {
+ content: '\e6ee';
+}
+.cicon-equalizer:before {
+ content: '\e802';
+}
+.cicon-eraser:before {
+ content: '\e770';
+}
+.cicon-eraser-o:before {
+ content: '\e772';
+}
+.cicon-evaluate:before {
+ content: '\e7f0';
+}
+.cicon-evaluate-o:before {
+ content: '\e700';
+}
+.cicon-event-close:before {
+ content: '\e6a5';
+}
+.cicon-event-done:before {
+ content: '\e6b2';
+}
+.cicon-event-list:before {
+ content: '\e6b8';
+}
+.cicon-explore:before {
+ content: '\e628';
+}
+.cicon-explore-line:before {
+ content: '\e719';
+}
+.cicon-explore-line-o:before {
+ content: '\e710';
+}
+.cicon-explore-o:before {
+ content: '\e626';
+}
+.cicon-extension:before {
+ content: '\e620';
+}
+.cicon-extension-o:before {
+ content: '\e63f';
+}
+.cicon-eye:before {
+ content: '\e740';
+}
+.cicon-eye-favor:before {
+ content: '\e7b4';
+}
+.cicon-eye-favor-o:before {
+ content: '\e7b5';
+}
+.cicon-eye-o:before {
+ content: '\e741';
+}
+.cicon-eye-off:before {
+ content: '\e7b3';
+}
+.cicon-eye-off-o:before {
+ content: '\e7b2';
+}
+.cicon-facebook:before {
+ content: '\e6ea';
+}
+.cicon-favorite:before {
+ content: '\e623';
+}
+.cicon-favorite-o:before {
+ content: '\e621';
+}
+.cicon-female:before {
+ content: '\e72f';
+}
+.cicon-file:before {
+ content: '\e857';
+}
+.cicon-file-copy:before {
+ content: '\e85c';
+}
+.cicon-file-copy-o:before {
+ content: '\e7bc';
+}
+.cicon-file-o:before {
+ content: '\e7bb';
+}
+.cicon-file-text:before {
+ content: '\e858';
+}
+.cicon-file-text-o:before {
+ content: '\e7b9';
+}
+.cicon-filter:before {
+ content: '\e6ec';
+}
+.cicon-fingerprint:before {
+ content: '\e63b';
+}
+.cicon-first-page:before {
+ content: '\e60c';
+}
+.cicon-flag:before {
+ content: '\e64d';
+}
+.cicon-flag-o:before {
+ content: '\e64c';
+}
+.cicon-flash-close:before {
+ content: '\e73b';
+}
+.cicon-flash-off:before {
+ content: '\e6d5';
+}
+.cicon-flash-on:before {
+ content: '\e6dc';
+}
+.cicon-flash-open:before {
+ content: '\e74f';
+}
+.cicon-folder:before {
+ content: '\e6a1';
+}
+.cicon-folder-add:before {
+ content: '\e6b4';
+}
+.cicon-folder-o:before {
+ content: '\e6b0';
+}
+.cicon-folder-special:before {
+ content: '\e65c';
+}
+.cicon-forward:before {
+ content: '\e601';
+}
+.cicon-fullscreen:before {
+ content: '\e915';
+}
+.cicon-fullscreen-exit:before {
+ content: '\e914';
+}
+.cicon-game:before {
+ content: '\e6c0';
+}
+.cicon-game-o:before {
+ content: '\e6d1';
+}
+.cicon-git-commit:before {
+ content: '\e7be';
+}
+.cicon-git-commit-o:before {
+ content: '\e7bd';
+}
+.cicon-github:before {
+ content: '\e6e9';
+}
+.cicon-github-circle:before {
+ content: '\ead8';
+}
+.cicon-goods:before {
+ content: '\e778';
+}
+.cicon-goodsnew:before {
+ content: '\e7bf';
+}
+.cicon-goodsnew-o:before {
+ content: '\e7c0';
+}
+.cicon-goods-o:before {
+ content: '\e70f';
+}
+.cicon-GooglePlaylogo:before {
+ content: '\e6e5';
+}
+.cicon-grid:before {
+ content: '\e6ce';
+}
+.cicon-grid-o:before {
+ content: '\e6cc';
+}
+.cicon-group:before {
+ content: '\e7f5';
+}
+.cicon-group-o:before {
+ content: '\e753';
+}
+.cicon-guanli:before {
+ content: '\e750';
+}
+.cicon-headset:before {
+ content: '\e6a4';
+}
+.cicon-headset-mic:before {
+ content: '\e6b1';
+}
+.cicon-help:before {
+ content: '\e66b';
+}
+.cicon-help-o:before {
+ content: '\e65e';
+}
+.cicon-home:before {
+ content: '\e70d';
+}
+.cicon-home-2:before {
+ content: '\e6fd';
+}
+.cicon-home-2-o:before {
+ content: '\e6cf';
+}
+.cicon-home-3:before {
+ content: '\e6fc';
+}
+.cicon-home-3-o:before {
+ content: '\e6e0';
+}
+.cicon-home-4:before {
+ content: '\e732';
+}
+.cicon-home-4-o:before {
+ content: '\e6e6';
+}
+.cicon-home-community:before {
+ content: '\e799';
+}
+.cicon-home-dot:before {
+ content: '\e794';
+}
+.cicon-home-dot-o:before {
+ content: '\e797';
+}
+.cicon-home-line:before {
+ content: '\e793';
+}
+.cicon-home-line-o:before {
+ content: '\e792';
+}
+.cicon-home-o:before {
+ content: '\e70a';
+}
+.cicon-home-sm:before {
+ content: '\e798';
+}
+.cicon-home-smile:before {
+ content: '\e79c';
+}
+.cicon-home-smile-o:before {
+ content: '\e7a0';
+}
+.cicon-home-smline:before {
+ content: '\e791';
+}
+.cicon-home-smline-o:before {
+ content: '\e731';
+}
+.cicon-home-sm-o:before {
+ content: '\e79b';
+}
+.cicon-hotel:before {
+ content: '\e7a8';
+}
+.cicon-hotel-o:before {
+ content: '\e7a3';
+}
+.cicon-huohu:before {
+ content: '\e72e';
+}
+.cicon-IE:before {
+ content: '\e922';
+}
+.cicon-image-text:before {
+ content: '\e781';
+}
+.cicon-image-text-o:before {
+ content: '\e758';
+}
+.cicon-import-export:before {
+ content: '\e615';
+}
+.cicon-info:before {
+ content: '\e6ef';
+}
+.cicon-info-o:before {
+ content: '\e705';
+}
+.cicon-input:before {
+ content: '\e75f';
+}
+.cicon-input-o:before {
+ content: '\e6c8';
+}
+.cicon-keyboard:before {
+ content: '\e6b6';
+}
+.cicon-kinds:before {
+ content: '\e748';
+}
+.cicon-last-page:before {
+ content: '\e60d';
+}
+.cicon-layout:before {
+ content: '\e7e8';
+}
+.cicon-layout-o:before {
+ content: '\e7e7';
+}
+.cicon-LightMode:before {
+ content: '\e7ba';
+}
+.cicon-link:before {
+ content: '\e6ab';
+}
+.cicon-link-off:before {
+ content: '\e6b9';
+}
+.cicon-loader-fill:before {
+ content: '\e76d';
+}
+.cicon-loading:before {
+ content: '\e746';
+}
+.cicon-loading1:before {
+ content: '\e749';
+}
+.cicon-loading2:before {
+ content: '\e7f1';
+}
+.cicon-location-off:before {
+ content: '\e671';
+}
+.cicon-location-off-o:before {
+ content: '\e66d';
+}
+.cicon-location-on:before {
+ content: '\e65f';
+}
+.cicon-location-on-o:before {
+ content: '\e661';
+}
+.cicon-lock:before {
+ content: '\e6ad';
+}
+.cicon-lock-o:before {
+ content: '\e6b3';
+}
+.cicon-lock-open:before {
+ content: '\e6ba';
+}
+.cicon-logout:before {
+ content: '\e76e';
+}
+.cicon-loop:before {
+ content: '\e616';
+}
+.cicon-magic:before {
+ content: '\e6b7';
+}
+.cicon-magic-o:before {
+ content: '\e6c2';
+}
+.cicon-mail:before {
+ content: '\e6be';
+}
+.cicon-mail-o:before {
+ content: '\e6bc';
+}
+.cicon-male:before {
+ content: '\e730';
+}
+.cicon-mic:before {
+ content: '\e656';
+}
+.cicon-mic-none:before {
+ content: '\e642';
+}
+.cicon-mic-off:before {
+ content: '\e652';
+}
+.cicon-miniprogram:before {
+ content: '\e7d6';
+}
+.cicon-mobile:before {
+ content: '\e854';
+}
+.cicon-mobile-o:before {
+ content: '\e7b6';
+}
+.cicon-moneybag:before {
+ content: '\e7ce';
+}
+.cicon-moneybag-o:before {
+ content: '\e7d1';
+}
+.cicon-more:before {
+ content: '\e688';
+}
+.cicon-more-tag:before {
+ content: '\e672';
+}
+.cicon-move:before {
+ content: '\e768';
+}
+.cicon-move-round:before {
+ content: '\e602';
+}
+.cicon-move-round-o:before {
+ content: '\e603';
+}
+.cicon-music:before {
+ content: '\e795';
+}
+.cicon-music-off:before {
+ content: '\e796';
+}
+.cicon-my:before {
+ content: '\e78c';
+}
+.cicon-my-o:before {
+ content: '\e78b';
+}
+.cicon-near-me:before {
+ content: '\e654';
+}
+.cicon-near-me-o:before {
+ content: '\e649';
+}
+.cicon-not:before {
+ content: '\e667';
+}
+.cicon-notice:before {
+ content: '\e666';
+}
+.cicon-notice-active:before {
+ content: '\e66f';
+}
+.cicon-notice-active-o:before {
+ content: '\e65d';
+}
+.cicon-notice-o:before {
+ content: '\e664';
+}
+.cicon-notice-off:before {
+ content: '\e6b5';
+}
+.cicon-notice-off-o:before {
+ content: '\e6bb';
+}
+.cicon-numcode:before {
+ content: '\e755';
+}
+.cicon-order:before {
+ content: '\e786';
+}
+.cicon-order-o:before {
+ content: '\e7b8';
+}
+.cicon-paint:before {
+ content: '\e75d';
+}
+.cicon-paint-o:before {
+ content: '\e75a';
+}
+.cicon-palette:before {
+ content: '\e696';
+}
+.cicon-palette-o:before {
+ content: '\e691';
+}
+.cicon-pause:before {
+ content: '\e669';
+}
+.cicon-pause-circle:before {
+ content: '\e678';
+}
+.cicon-person:before {
+ content: '\e679';
+}
+.cicon-person-add:before {
+ content: '\e668';
+}
+.cicon-person-add-o:before {
+ content: '\e66a';
+}
+.cicon-person-o:before {
+ content: '\e67d';
+}
+.cicon-person-pin-circle:before {
+ content: '\e66c';
+}
+.cicon-person-pin-circle-o:before {
+ content: '\e670';
+}
+.cicon-phone:before {
+ content: '\e6f0';
+}
+.cicon-phone-call:before {
+ content: '\e6d7';
+}
+.cicon-pic:before {
+ content: '\e756';
+}
+.cicon-pic-o:before {
+ content: '\e69e';
+}
+.cicon-pin-drop:before {
+ content: '\e648';
+}
+.cicon-pin-drop-o:before {
+ content: '\e655';
+}
+.cicon-place:before {
+ content: '\e651';
+}
+.cicon-place-o:before {
+ content: '\e650';
+}
+.cicon-play-arrow:before {
+ content: '\e66e';
+}
+.cicon-play-circle:before {
+ content: '\e674';
+}
+.cicon-play-circle-o:before {
+ content: '\e67f';
+}
+.cicon-popover:before {
+ content: '\e74e';
+}
+.cicon-popover-o:before {
+ content: '\e757';
+}
+.cicon-present:before {
+ content: '\e73a';
+}
+.cicon-present-o:before {
+ content: '\e711';
+}
+.cicon-progress:before {
+ content: '\e784';
+}
+.cicon-qq:before {
+ content: '\e7d9';
+}
+.cicon-qr-code-fill:before {
+ content: '\e767';
+}
+.cicon-qr-code-line:before {
+ content: '\e75e';
+}
+.cicon-quill:before {
+ content: '\e760';
+}
+.cicon-quill-o:before {
+ content: '\e761';
+}
+.cicon-radio:before {
+ content: '\e6d4';
+}
+.cicon-radiobox:before {
+ content: '\e763';
+}
+.cicon-radiobox-o:before {
+ content: '\e75b';
+}
+.cicon-recharge:before {
+ content: '\e71c';
+}
+.cicon-recharge-o:before {
+ content: '\e71d';
+}
+.cicon-record:before {
+ content: '\e7a4';
+}
+.cicon-record-o:before {
+ content: '\e7a6';
+}
+.cicon-redo:before {
+ content: '\e612';
+}
+.cicon-redpacket:before {
+ content: '\e7d3';
+}
+.cicon-redpacket-o:before {
+ content: '\e71a';
+}
+.cicon-refresh:before {
+ content: '\e611';
+}
+.cicon-repair:before {
+ content: '\e73f';
+}
+.cicon-repair-o:before {
+ content: '\e73e';
+}
+.cicon-repeat:before {
+ content: '\e617';
+}
+.cicon-replay:before {
+ content: '\e619';
+}
+.cicon-reply:before {
+ content: '\e618';
+}
+.cicon-reply-all:before {
+ content: '\e614';
+}
+.cicon-road-map:before {
+ content: '\e769';
+}
+.cicon-road-map-o:before {
+ content: '\e76a';
+}
+.cicon-round:before {
+ content: '\e716';
+}
+.cicon-round-angle:before {
+ content: '\e6f5';
+}
+.cicon-round-angle-o:before {
+ content: '\e6f6';
+}
+.cicon-round-arrow-line:before {
+ content: '\e734';
+}
+.cicon-round-box:before {
+ content: '\e604';
+}
+.cicon-safe:before {
+ content: '\e77f';
+}
+.cicon-safe-check:before {
+ content: '\e875';
+}
+.cicon-safe-check-o:before {
+ content: '\e876';
+}
+.cicon-safe-flash:before {
+ content: '\e783';
+}
+.cicon-safe-flash-o:before {
+ content: '\e775';
+}
+.cicon-safe-key:before {
+ content: '\e76c';
+}
+.cicon-safe-key-o:before {
+ content: '\e766';
+}
+.cicon-safe-o:before {
+ content: '\e77e';
+}
+.cicon-save:before {
+ content: '\e677';
+}
+.cicon-save-o:before {
+ content: '\e684';
+}
+.cicon-scan:before {
+ content: '\e703';
+}
+.cicon-scissors:before {
+ content: '\e762';
+}
+.cicon-search:before {
+ content: '\e6f7';
+}
+.cicon-search-line:before {
+ content: '\e771';
+}
+.cicon-searchlist:before {
+ content: '\e720';
+}
+.cicon-search-o:before {
+ content: '\e782';
+}
+.cicon-search-sm:before {
+ content: '\e631';
+}
+.cicon-service:before {
+ content: '\e73d';
+}
+.cicon-service-fill:before {
+ content: '\e704';
+}
+.cicon-service-o:before {
+ content: '\e721';
+}
+.cicon-set:before {
+ content: '\e773';
+}
+.cicon-set-list:before {
+ content: '\e76f';
+}
+.cicon-set-o:before {
+ content: '\e774';
+}
+.cicon-settings:before {
+ content: '\e77a';
+}
+.cicon-settings-o:before {
+ content: '\e780';
+}
+.cicon-share:before {
+ content: '\e6c5';
+}
+.cicon-share-line-o:before {
+ content: '\e74d';
+}
+.cicon-shengji:before {
+ content: '\e747';
+}
+.cicon-shopping-cart:before {
+ content: '\e685';
+}
+.cicon-shopping-cart-o:before {
+ content: '\e676';
+}
+.cicon-show:before {
+ content: '\e785';
+}
+.cicon-show-o:before {
+ content: '\e787';
+}
+.cicon-shuffle:before {
+ content: '\e61a';
+}
+.cicon-sip:before {
+ content: '\e764';
+}
+.cicon-sip-o:before {
+ content: '\e765';
+}
+.cicon-skip-next:before {
+ content: '\e6dd';
+}
+.cicon-skip-previous:before {
+ content: '\e6d6';
+}
+.cicon-slack:before {
+ content: '\e87b';
+}
+.cicon-slack-square:before {
+ content: '\e891';
+}
+.cicon-sort:before {
+ content: '\e6bf';
+}
+.cicon-sort-order:before {
+ content: '\e6fe';
+}
+.cicon-sound:before {
+ content: '\e77b';
+}
+.cicon-sponsor:before {
+ content: '\e77c';
+}
+.cicon-sponsor-o:before {
+ content: '\e77d';
+}
+.cicon-star:before {
+ content: '\e683';
+}
+.cicon-star-half:before {
+ content: '\e67c';
+}
+.cicon-star-o:before {
+ content: '\e67b';
+}
+.cicon-stock:before {
+ content: '\e789';
+}
+.cicon-stop:before {
+ content: '\e6db';
+}
+.cicon-store:before {
+ content: '\e7ac';
+}
+.cicon-store-0:before {
+ content: '\e7ab';
+}
+.cicon-store-2:before {
+ content: '\e7a7';
+}
+.cicon-store-2-o:before {
+ content: '\e7a5';
+}
+.cicon-sub-left:before {
+ content: '\e60b';
+}
+.cicon-sub-right:before {
+ content: '\e60f';
+}
+.cicon-subtitles:before {
+ content: '\e6da';
+}
+.cicon-subtitles-o:before {
+ content: '\e6d8';
+}
+.cicon-sync-alt:before {
+ content: '\e613';
+}
+.cicon-tag:before {
+ content: '\e751';
+}
+.cicon-tag-o:before {
+ content: '\e752';
+}
+.cicon-taobao:before {
+ content: '\e712';
+}
+.cicon-terminal:before {
+ content: '\e7c1';
+}
+.cicon-terminal-o:before {
+ content: '\e7c2';
+}
+.cicon-thumb-down:before {
+ content: '\e6c1';
+}
+.cicon-thumb-down-o:before {
+ content: '\e6c9';
+}
+.cicon-thumb-up:before {
+ content: '\e6c6';
+}
+.cicon-thumb-up-line:before {
+ content: '\e71b';
+}
+.cicon-thumb-up-line-o:before {
+ content: '\e6eb';
+}
+.cicon-thumb-up-o:before {
+ content: '\e6cb';
+}
+.cicon-ticket:before {
+ content: '\e800';
+}
+.cicon-ticket-o:before {
+ content: '\e701';
+}
+.cicon-time:before {
+ content: '\e6f8';
+}
+.cicon-time-o:before {
+ content: '\e6f9';
+}
+.cicon-timer:before {
+ content: '\e69c';
+}
+.cicon-title:before {
+ content: '\e82f';
+}
+.cicon-titles:before {
+ content: '\e745';
+}
+.cicon-toggle:before {
+ content: '\e706';
+}
+.cicon-toggle-o:before {
+ content: '\e733';
+}
+.cicon-topbar:before {
+ content: '\e788';
+}
+.cicon-translate:before {
+ content: '\e79a';
+}
+.cicon-tree:before {
+ content: '\e659';
+}
+.cicon-Tt:before {
+ content: '\e7a2';
+}
+.cicon-twiter:before {
+ content: '\e6e8';
+}
+.cicon-cicon-community-o:before {
+ content: '\e6df';
+}
+.cicon-undo:before {
+ content: '\e61b';
+}
+.cicon-unfold-less:before {
+ content: '\e60e';
+}
+.cicon-unfold-more:before {
+ content: '\e609';
+}
+.cicon-upstage:before {
+ content: '\e724';
+}
+.cicon-upstage-o:before {
+ content: '\e725';
+}
+.cicon-view-agenda:before {
+ content: '\e639';
+}
+.cicon-view-array:before {
+ content: '\e636';
+}
+.cicon-view-carousel:before {
+ content: '\e638';
+}
+.cicon-view-column:before {
+ content: '\e632';
+}
+.cicon-view-day:before {
+ content: '\e627';
+}
+.cicon-view-headline:before {
+ content: '\e62b';
+}
+.cicon-view-list:before {
+ content: '\e63c';
+}
+.cicon-view-module:before {
+ content: '\e629';
+}
+.cicon-view-quilt:before {
+ content: '\e630';
+}
+.cicon-volume:before {
+ content: '\e6c4';
+}
+.cicon-volume-off:before {
+ content: '\e6cd';
+}
+.cicon-warn:before {
+ content: '\e662';
+}
+.cicon-warn-o:before {
+ content: '\e675';
+}
+.cicon-wechat-pay:before {
+ content: '\e7e6';
+}
+.cicon-weibo-fill:before {
+ content: '\e7e4';
+}
+.cicon-weibo-o:before {
+ content: '\e7e3';
+}
+.cicon-weixin:before {
+ content: '\e6de';
+}
+.cicon-whatshot:before {
+ content: '\e6ca';
+}
+.cicon-whatshot-o:before {
+ content: '\e6d0';
+}
+.cicon-wifi:before {
+ content: '\e6d2';
+}
+.cicon-wifi-off:before {
+ content: '\e6d3';
+}
+.cicon-yamaxun:before {
+ content: '\e7b7';
+}
+.cicon-zuoji:before {
+ content: '\e754';
+}
diff --git a/sheep/scss/icon/_icon.scss b/sheep/scss/icon/_icon.scss
new file mode 100644
index 00000000..f277fc8d
--- /dev/null
+++ b/sheep/scss/icon/_icon.scss
@@ -0,0 +1,181 @@
+@font-face {
+ font-family: 'colorui'; /* Project id 2620914 */
+ src: url('data:application/x-font-woff2;charset=utf-8;base64,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')
+ format('woff2');
+ /* #ifdef MP-ALIPAY */
+ src: url('//at.alicdn.com/t/font_2620914_57y9q5zpbel.woff?t=1624238023908') format('woff'),
+ url('//at.alicdn.com/t/font_2620914_57y9q5zpbel.ttf?t=1624238023908') format('truetype');
+ /* #endif */
+}
+
+[class*='_icon-'] {
+ font-family: 'colorui' !important;
+ display: inline-block;
+}
+@font-face {
+ font-family: 'ui-num';
+ src: url('data:application/x-font-ttf;base64,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')
+ format('woff2');
+ font-weight: normal;
+ font-style: normal;
+}
+
+._icon-checkbox:before {
+ content: '\e713';
+}
+
+._icon-box:before {
+ content: '\e714';
+}
+
+._icon-checkbox-o:before {
+ content: '\e715';
+}
+
+._icon-round:before {
+ content: '\e716';
+}
+
+._icon-home-o:before {
+ content: '\e70a';
+}
+
+._icon-home:before {
+ content: '\e70d';
+}
+
+._icon-edit:before {
+ content: '\e649';
+}
+
+._icon-close:before {
+ content: '\e6ed';
+}
+
+._icon-check-round:before {
+ content: '\e6f1';
+}
+
+._icon-check-round-o:before {
+ content: '\e6f2';
+}
+
+._icon-close-round:before {
+ content: '\e6f3';
+}
+
+._icon-close-round-o:before {
+ content: '\e6f4';
+}
+
+._icon-waiting:before {
+ content: '\e6f8';
+}
+
+._icon-waiting-o:before {
+ content: '\e6f9';
+}
+
+._icon-warn:before {
+ content: '\e662';
+}
+
+._icon-warn-o:before {
+ content: '\e675';
+}
+
+._icon-more:before {
+ content: '\e688';
+}
+
+._icon-delete:before {
+ content: '\e707';
+}
+
+._icon-delete-o:before {
+ content: '\e709';
+}
+
+._icon-add-round:before {
+ content: '\e717';
+}
+
+._icon-add-round-o:before {
+ content: '\e718';
+}
+
+._icon-add:before {
+ content: '\e6e4';
+}
+
+._icon-info:before {
+ content: '\e6ef';
+}
+
+._icon-info-o:before {
+ content: '\e705';
+}
+
+._icon-move:before {
+ content: '\e768';
+}
+
+._icon-title:before {
+ content: '\e82f';
+}
+
+._icon-titles:before {
+ content: '\e745';
+}
+
+._icon-loading:before {
+ content: '\e746';
+}
+
+._icon-copy-o:before {
+ content: '\e7bc';
+}
+
+._icon-copy:before {
+ content: '\e85c';
+}
+
+._icon-loader:before {
+ content: '\e76d';
+}
+
+._icon-search:before {
+ content: '\e782';
+}
+
+._icon-back:before {
+ content: '\e600';
+}
+
+._icon-forward:before {
+ content: '\e601';
+}
+
+._icon-arrow:before {
+ content: '\e608';
+}
+
+._icon-drop-down:before {
+ content: '\e61c';
+}
+
+._icon-drop-up:before {
+ content: '\e61d';
+}
+
+._icon-check:before {
+ content: '\e69f';
+}
+
+._icon-move-round:before {
+ content: '\e602';
+}
+
+._icon-move-round-o:before {
+ content: '\e603';
+}
diff --git a/sheep/scss/icon/_sheepicon.scss b/sheep/scss/icon/_sheepicon.scss
new file mode 100644
index 00000000..cf7ea082
--- /dev/null
+++ b/sheep/scss/icon/_sheepicon.scss
@@ -0,0 +1,94 @@
+@font-face {
+ font-family: 'sheepicon';
+ src: url('data:application/x-font-woff2;charset=utf-8;base64,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')
+ format('woff2');
+}
+
+[class*='sicon-'] {
+ font-family: 'sheepicon';
+ display: inline-block;
+}
+
+.sicon-edit:before {
+ content: '\e711';
+}
+
+.sicon-basic:before {
+ content: '\e712';
+}
+
+.sicon-home:before {
+ content: '\e70c';
+}
+
+.sicon-more:before {
+ content: '\e707';
+}
+
+.sicon-check-line:before {
+ content: '\e708';
+}
+
+.sicon-transport:before {
+ content: '\e709';
+}
+
+.sicon-goods-card:before {
+ content: '\e70a';
+}
+
+.sicon-collect:before {
+ content: '\e70b';
+}
+
+.sicon-warning-line:before {
+ content: '\e70d';
+}
+
+.sicon-score1:before {
+ content: '\e70e';
+}
+
+.sicon-score2:before {
+ content: '\e70f';
+}
+
+.sicon-goods-list:before {
+ content: '\e710';
+}
+
+.sicon-back:before {
+ content: '\e706';
+}
+
+.sicon-unchecked:before {
+ content: '\e703';
+}
+
+.sicon-warning-outline:before {
+ content: '\e6ff';
+}
+
+.sicon-question-outline:before {
+ content: '\e700';
+}
+
+.sicon-circlecheck:before {
+ content: '\e701';
+}
+
+.sicon-circleclose:before {
+ content: '\e702';
+}
+
+.sicon-delivery:before {
+ content: '\e6fd';
+}
+
+.sicon-orders:before {
+ content: '\e6fe';
+}
+
+.sicon-qrcode:before {
+ content: '\e6f9';
+}
diff --git a/sheep/scss/icon/_style.scss b/sheep/scss/icon/_style.scss
new file mode 100644
index 00000000..a2c4dc82
--- /dev/null
+++ b/sheep/scss/icon/_style.scss
@@ -0,0 +1,43 @@
+@import './icon'; //核心图标库
+@import './coloricon'; //扩展图标库
+@import './sheepicon';
+.icon-spin {
+ animation: icon-spin 2s infinite linear;
+}
+
+.icon-pulse {
+ animation: icon-spin 1s infinite steps(8);
+}
+
+@keyframes icon-spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(359deg);
+ }
+}
+.icon-90 {
+ transform: rotate(90deg);
+}
+.icon-180 {
+ transform: rotate(180deg);
+}
+.icon-270 {
+ transform: rotate(270deg);
+}
+.icon-x {
+ transform: scale(-1, 1);
+}
+.icon-y {
+ transform: scale(1, -1);
+}
+.icon-fw {
+ width: calc(18em / 14);
+ text-align: center;
+}
+@each $class, $value in $iconsize {
+ .icon-#{$class} {
+ transform: scale(#{$value});
+ }
+}
diff --git a/sheep/scss/index.scss b/sheep/scss/index.scss
new file mode 100644
index 00000000..c8419560
--- /dev/null
+++ b/sheep/scss/index.scss
@@ -0,0 +1,27 @@
+@import './tools';
+@import './ui';
+
+/* 字体文件 */
+@font-face {
+ font-family: OPPOSANS;
+ src: url('~@/sheep/scss/font/OPPOSANS-M-subfont.ttf');
+}
+.font-OPPOSANS {
+ font-family: OPPOSANS;
+}
+page {
+ -webkit-overflow-scrolling: touch; // 解决ios滑动不流畅
+ height: 100%;
+ width: 100%;
+ // font-family: OPPOSANS;
+ word-break: break-all; //英文文本不换行
+ white-space: normal;
+ background-color: $bg-page;
+ color: $dark-3;
+}
+::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+ color: transparent;
+ display: none;
+}
diff --git a/sheep/scss/style/_avatar.scss b/sheep/scss/style/_avatar.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/sheep/scss/style/_background.scss b/sheep/scss/style/_background.scss
new file mode 100644
index 00000000..775f37f1
--- /dev/null
+++ b/sheep/scss/style/_background.scss
@@ -0,0 +1,204 @@
+/* ==================
+ 背景
+ ==================== */
+/* -- 基础色 -- */
+@each $color, $value in map-merge($colors, $darks) {
+ .bg-#{$color} {
+ background-color: $value !important;
+ @if $color == 'yellow' {
+ color: #333333 !important;
+ } @else {
+ color: #ffffff !important;
+ }
+ }
+}
+
+/* -- 浅色 -- */
+@each $color, $value in $colors {
+ .bg-#{$color}-light {
+ background-image: linear-gradient(45deg, white, mix(white, $value, 85%)) !important;
+ color: $value !important;
+ }
+
+ .bg-#{$color}-thin {
+ background-color: rgba($value, var(--ui-BG-opacity)) !important;
+ color: $value !important;
+ }
+}
+
+/* -- 渐变色 -- */
+
+@each $color, $value in $colors {
+ @each $colorsub, $valuesub in $colors {
+ @if $color != $colorsub {
+ .bg-#{$color}-#{$colorsub} {
+ // background-color: $value !important;
+ background-image: linear-gradient(130deg, $value, $valuesub) !important;
+ color: #ffffff !important;
+ }
+ }
+ }
+}
+.bg-yellow-gradient {
+ background-image: linear-gradient(45deg, #f5fe00, #ff6600) !important;
+ color: $dark-3 !important;
+}
+.bg-orange-gradient {
+ background-image: linear-gradient(90deg, #ff6000, #fe832a) !important;
+ color: $white !important;
+}
+.bg-red-gradient {
+ background-image: linear-gradient(45deg, #f33a41, #ed0586) !important;
+ color: $white !important;
+}
+.bg-pink-gradient {
+ background-image: linear-gradient(45deg, #fea894, #ff1047) !important;
+ color: $white !important;
+}
+.bg-mauve-gradient {
+ background-image: linear-gradient(45deg, #c01f95, #7115cc) !important;
+ color: $white !important;
+}
+.bg-purple-gradient {
+ background-image: linear-gradient(45deg, #9829ea, #5908fb) !important;
+ color: $white !important;
+}
+.bg-blue-gradient {
+ background-image: linear-gradient(45deg, #00b8f9, #0166eb) !important;
+ color: $white !important;
+}
+.bg-cyan-gradient {
+ background-image: linear-gradient(45deg, #06edfe, #48b2fe) !important;
+ color: $white !important;
+}
+.bg-green-gradient {
+ background-image: linear-gradient(45deg, #3ab54a, #8cc63f) !important;
+ color: $white !important;
+}
+.bg-olive-gradient {
+ background-image: linear-gradient(45deg, #90e630, #39d266) !important;
+ color: $white !important;
+}
+.bg-grey-gradient {
+ background-image: linear-gradient(45deg, #9aadb9, #354855) !important;
+ color: $white !important;
+}
+.bg-brown-gradient {
+ background-image: linear-gradient(45deg, #ca6f2e, #cb1413) !important;
+ color: $white !important;
+}
+
+@each $color, $value in $grays {
+ .bg-#{$color} {
+ background-color: $value !important;
+ color: #333333 !important;
+ }
+}
+
+.bg-square {
+ @include bg-square;
+}
+.bg-none {
+ background: transparent !important;
+ color: inherit !important;
+}
+
+[class*='bg-mask'] {
+ position: relative;
+ //background: transparent !important;
+ color: #ffffff !important;
+ > view,
+ > text {
+ position: relative;
+ z-index: 1;
+ color: #ffffff;
+ }
+ &::before {
+ content: '';
+ border-radius: inherit;
+ width: 100%;
+ height: 100%;
+ @include position-center;
+ background-color: rgba(0, 0, 0, 0.4);
+ z-index: 0;
+ }
+ @at-root .bg-mask-80::before {
+ background: rgba(0, 0, 0, 0.8) !important;
+ }
+ @at-root .bg-mask-50::before {
+ background: rgba(0, 0, 0, 0.5) !important;
+ }
+ @at-root .bg-mask-20::before {
+ background: rgba(0, 0, 0, 0.2) !important;
+ }
+ @at-root .bg-mask-top::before {
+ background-color: rgba(0, 0, 0, 0);
+ background-image: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0.618), rgba(0, 0, 0, 0.01));
+ }
+ @at-root .bg-white-top {
+ background-color: rgba(0, 0, 0, 0);
+ background-image: linear-gradient(rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.3));
+ }
+ @at-root .bg-mask-bottom::before {
+ background-color: rgba(0, 0, 0, 0);
+ background-image: linear-gradient(rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 0.618), rgba(0, 0, 0, 1));
+ }
+}
+.bg-img {
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+[class*='bg-blur'] {
+ position: relative;
+ > view,
+ > text {
+ position: relative;
+ z-index: 1;
+ }
+ &::before {
+ content: '';
+ width: 100%;
+ height: 100%;
+ @include position-center;
+ border-radius: inherit;
+ transform-origin: 0 0;
+ pointer-events: none;
+ box-sizing: border-box;
+ }
+}
+@supports (-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px)) {
+ .bg-blur::before {
+ @include blur;
+ background-color: var(--ui-Blur-1);
+ }
+ .bg-blur-1::before {
+ @include blur;
+ background-color: var(--ui-Blur-2);
+ }
+ .bg-blur-2::before {
+ @include blur;
+ background-color: var(--ui-Blur-3);
+ }
+}
+@supports not (backdrop-filter: blur(5px)) {
+ .bg-blur {
+ color: var(--ui-TC);
+ &::before {
+ background-color: var(--ui-BG);
+ }
+ }
+ .bg-blur-1 {
+ color: var(--ui-TC);
+ &::before {
+ background-color: var(--ui-BG-1);
+ }
+ }
+ .bg-blur-2 {
+ color: var(--ui-TC);
+ &::before {
+ background-color: var(--ui-BG-2);
+ }
+ }
+}
diff --git a/sheep/scss/style/_border.scss b/sheep/scss/style/_border.scss
new file mode 100644
index 00000000..4ef1d54b
--- /dev/null
+++ b/sheep/scss/style/_border.scss
@@ -0,0 +1,140 @@
+/* ==================
+ 边框
+ ==================== */
+/* -- 实线 -- */
+.border {
+ overflow: initial !important;
+ @at-root [class*='border'],
+ [class*='dashed'] {
+ position: relative;
+ &.dline {
+ --ui-Border: var(--ui-BG-3);
+ }
+ &::after {
+ content: ' ';
+ width: 200%;
+ height: 200%;
+ position: absolute;
+ z-index: 0;
+ top: 0;
+ left: 0;
+ transform: scale(0.5);
+ transform-origin: 0 0;
+ pointer-events: none;
+ box-sizing: border-box;
+ border-radius: inherit;
+ }
+ &.radius::after {
+ border-radius: calc(#{$radius} * 2);
+ }
+ &.round::after {
+ border-radius: #{$round-pill};
+ }
+ }
+ &::after {
+ border: 1px solid var(--ui-Border);
+ }
+ &s::after {
+ border: 4rpx solid var(--ui-Border);
+ }
+ &ss::after {
+ border: 8rpx solid var(--ui-Border);
+ }
+ @each $value in (top, right, bottom, left) {
+ &-#{$value}::after {
+ border-#{$value}: 1px solid var(--ui-Border);
+ }
+ &s-#{$value}::after {
+ border-#{$value}: 4rpx solid var(--ui-Border);
+ }
+ &ss-#{$value}::after {
+ border-#{$value}: 8rpx solid var(--ui-Border);
+ }
+ }
+}
+/* -- 虚线 -- */
+.dashed {
+ &::after {
+ border: 4rpx dashed var(--ui-Border);
+ }
+ &s::after {
+ border: 6rpx dashed var(--ui-Border);
+ }
+ @each $value in (top, right, bottom, left) {
+ &-#{$value}::after {
+ border-#{$value}: 4rpx dashed var(--ui-Border);
+ }
+ &s-#{$value}::after {
+ border-#{$value}: 6rpx dashed var(--ui-Border);
+ }
+ }
+}
+@each $color, $value in map-merge($colors, map-merge($darks, $grays)) {
+ .border-#{$color}::after,
+ .border-#{$color}[class*='-shine']::before {
+ border-color: $value !important;
+ }
+}
+@each $value in (a, b, c, d, e) {
+ .main-#{$value}-border::after,
+ .main-#{$value}-border[class*='-shine']::before {
+ border-color: var(--main-#{$value}) !important;
+ }
+}
+.dashed-shine,
+.dasheds-shine {
+ position: relative;
+ overflow: hidden;
+ &::after,
+ &::before {
+ border-style: dashed;
+ border-color: var(--ui-Border);
+ animation: shineafter 1s infinite linear;
+ width: calc(200% + 40px);
+ height: 200%;
+ border-width: 2px 0;
+ }
+ &::before {
+ content: ' ';
+ position: absolute;
+ transform: scale(0.5);
+ transform-origin: 0 0;
+ pointer-events: none;
+ box-sizing: border-box;
+ animation: shinebefore 1s infinite linear;
+ width: 200%;
+ height: calc(200% + 40px);
+ border-width: 0 2px;
+ }
+}
+.dasheds-shine {
+ &::after,
+ &::before {
+ border-width: 4px 0;
+ }
+ &::before {
+ border-width: 0 4px;
+ }
+}
+
+@keyframes shineafter {
+ 0% {
+ top: 0;
+ left: -22px;
+ }
+ 100% {
+ top: 0px;
+ left: 0px;
+ }
+}
+
+@keyframes shinebefore {
+ 0% {
+ top: -22px;
+ left: 0;
+ }
+ 100% {
+ top: 0px;
+ left: 0px;
+ }
+}
diff --git a/sheep/scss/style/_button.scss b/sheep/scss/style/_button.scss
new file mode 100644
index 00000000..70693457
--- /dev/null
+++ b/sheep/scss/style/_button.scss
@@ -0,0 +1,87 @@
+.ui-btn-box {
+ display: inline-block;
+}
+.ui-btn {
+ position: relative;
+ border: 0rpx;
+ display: inline-block;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ padding: 0.7857em 1.5em 0.7857em;
+ font-size: 28rpx;
+ line-height: 1em;
+ text-align: center;
+ text-decoration: none;
+ overflow: visible;
+ margin: 0 0.25em 0 0;
+ transform: translate(0rpx, 0rpx);
+ border-radius: $radius;
+ white-space: nowrap;
+ color: var(--text-a);
+ background-color: var(--ui-BG);
+ vertical-align: baseline;
+ &:first-child:last-child {
+ margin: 0;
+ }
+ &:not([class*='round'])::after {
+ border-radius: calc(#{$radius} * 2);
+ }
+ &:not([class*='border'])::after {
+ // content: ' ';
+ // width: 200%;
+ // height: 200%;
+ // display: block;
+ // position: absolute;
+ // z-index: 0;
+ // top: 0;
+ // left: 0;
+ // transform: scale(0.5);
+ // transform-origin: 0 0;
+ // pointer-events: none;
+ // box-sizing: border-box;
+ display: none;
+ }
+ &.round::after {
+ border-radius: #{$round-pill};
+ }
+ &.icon {
+ padding: 0.8em 0.8em;
+ }
+
+ &.sm {
+ font-size: 24rpx;
+ }
+
+ &.lg {
+ font-size: 32rpx;
+ }
+
+ &.xl {
+ font-size: 36rpx;
+ }
+
+ &.block {
+ width: 100%;
+ display: block;
+ font-size: 32rpx;
+ }
+
+ &[disabled] {
+ opacity: 0.6;
+ }
+
+ &.none-style {
+ background-color: transparent !important;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ display: flex;
+ }
+}
+
+.ui-btn:not(.icon) [class*='icon-'] {
+ margin: 0 0.25em;
+}
diff --git a/sheep/scss/style/_card.scss b/sheep/scss/style/_card.scss
new file mode 100644
index 00000000..17aa6b3a
--- /dev/null
+++ b/sheep/scss/style/_card.scss
@@ -0,0 +1,353 @@
+/* ==================
+ 卡片
+ ==================== */
+
+.ui-cards {
+ display: block;
+ overflow: hidden;
+ & .ui-btn.badge {
+ top: 0;
+ right: 0;
+ font-size: 24rpx;
+ padding: 0rpx 15rpx;
+ height: 40rpx;
+ }
+ &.no-card > .ui-item {
+ margin: 0rpx;
+ border-radius: 0rpx;
+ }
+ & > .ui-item {
+ display: block;
+ overflow: hidden;
+ border-radius: 10rpx;
+ margin: 30rpx;
+ }
+ & > .ui-item.shadow-blur {
+ overflow: initial;
+ }
+ .grid.grid-square {
+ margin-bottom: -20rpx;
+ }
+ &.article {
+ display: block;
+ & > .ui-item {
+ padding: 30rpx;
+ background-color: var(--box-bg);
+ display: flex;
+ align-items: flex-start;
+ }
+ & > .time {
+ padding: 30rpx 0 0 30rpx;
+ }
+ & > .ui-item .title {
+ font-size: 30rpx;
+ font-weight: 900;
+ color: #333333;
+ }
+ & > .ui-item .content {
+ flex: 1;
+ }
+ & > .ui-item > image {
+ width: 240rpx;
+ height: 6.4em;
+ margin-left: 20rpx;
+ border-radius: 6rpx;
+ }
+ & > .ui-item .content .desc {
+ font-size: 12px;
+ color: var(--text-c);
+ }
+ & > .ui-item .content .text-content {
+ font-size: 28rpx;
+ color: #888;
+ }
+ }
+ &.case {
+ .image {
+ position: relative;
+ image {
+ width: 100%;
+ display: block;
+ }
+ .ui-tag {
+ position: absolute;
+ right: 0;
+ top: 0;
+ }
+ .ui-bar {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ background-color: transparent;
+ padding: 0rpx 30rpx;
+ }
+ .bg-black {
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ background-color: rgba(0, 0, 0, 0.6);
+ }
+ }
+ &.no-card .image {
+ margin: 30rpx 30rpx 0;
+ overflow: hidden;
+ border-radius: 10rpx;
+ }
+ }
+ &.dynamic {
+ display: block;
+ & > .ui-item {
+ display: block;
+ overflow: hidden;
+ & > .text-content {
+ padding: 0 30rpx 0;
+ font-size: 30rpx;
+ margin-bottom: 20rpx;
+ }
+ & .square-img {
+ width: 100%;
+ height: 200rpx;
+ border-radius: 6rpx;
+ }
+ & .only-img {
+ width: 100%;
+ height: 320rpx;
+ border-radius: 6rpx;
+ }
+ }
+ }
+ &.goods {
+ display: block;
+ & > .ui-item {
+ padding: 30rpx;
+ display: flex;
+ position: relative;
+ background-color: var(--ui-BG);
+ & + .ui-item {
+ border-top: 1rpx solid #eeeeee;
+ }
+ .content {
+ width: 410rpx;
+ padding: 0rpx;
+ }
+ .title {
+ font-size: 30rpx;
+ font-weight: 900;
+ color: #333333;
+ line-height: 1.4;
+ height: 1.4em;
+ overflow: hidden;
+ }
+ }
+ &.col-goods.col-twice {
+ display: flex;
+ flex-wrap: wrap;
+ padding-bottom: 30rpx;
+ & > .ui-item {
+ width: calc(50% - 30rpx);
+ margin: 20rpx 20rpx 0rpx 20rpx;
+ .content {
+ padding: 20rpx;
+ }
+ }
+ & > .ui-item:nth-child(2n) {
+ margin-left: 0rpx;
+ }
+ }
+ &.col-goods > .ui-item {
+ padding: 0rpx;
+ display: block;
+ border: 0px;
+ .content {
+ width: 100%;
+ padding: 30rpx;
+ }
+ }
+ &.no-card > .ui-item .content {
+ width: 470rpx;
+ padding: 0rpx;
+ }
+ &.no-card > .ui-item .title,
+ &.col-goods > .ui-item .title {
+ height: 3em;
+ overflow: hidden;
+ }
+ & > .ui-item .text-linecut-2 {
+ -webkit-line-clamp: 1;
+ }
+ &.no-card > .ui-item .text-linecut-2,
+ &.col-goods > .ui-item .text-linecut-2 {
+ -webkit-line-clamp: 2;
+ line-height: 1.6em;
+ height: 3.2em;
+ }
+ & > .ui-item > image {
+ width: 200rpx;
+ height: 200rpx;
+ margin-right: 20rpx;
+ border-radius: 6rpx;
+ }
+ &.no-card > .ui-item > image {
+ width: 220rpx;
+ height: 170rpx;
+ }
+ &.col-goods > .ui-item > image {
+ width: 100%;
+ height: 340rpx;
+ border-bottom-left-radius: 0rpx;
+ border-bottom-right-radius: 0rpx;
+ display: block;
+ }
+ &.col-goods.col-twice > .ui-item > image {
+ height: 236rpx;
+ }
+ }
+ &.loan {
+ display: block;
+ & > .ui-item {
+ padding: 30rpx 0 30rpx 30rpx;
+ display: flex;
+ position: relative;
+ background-color: var(--box-bg);
+
+ .content {
+ width: 450rpx;
+ padding: 0rpx;
+ .tag-list {
+ width: 450rpx;
+ display: flex;
+ flex-wrap: wrap;
+ font-size: 12px;
+ margin-top: 18rpx;
+ }
+ }
+ .action {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ }
+ }
+ }
+ &.houses {
+ display: block;
+ & > .ui-item {
+ padding: 20rpx;
+ display: flex;
+ position: relative;
+ background-color: var(--box-bg);
+ .image {
+ width: 230rpx;
+ height: 180rpx;
+ margin-right: 20rpx;
+ border-radius: 6rpx;
+ }
+ .content {
+ width: 400rpx;
+ padding: 0rpx;
+ .tag-list {
+ width: 400rpx;
+ display: flex;
+ flex-wrap: wrap;
+ font-size: 12px;
+ margin-top: 10rpx;
+ .ui-item {
+ height: 20px;
+ line-height: 20px;
+ }
+ }
+ }
+ .action {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ }
+ }
+ }
+
+ &.product {
+ display: flex;
+ flex-wrap: wrap;
+ padding-bottom: 30rpx;
+ & > .ui-item {
+ width: calc(100% - 15rpx);
+ margin: 20rpx 20rpx 0rpx 20rpx;
+ background-color: var(--box-bg);
+ position: relative;
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+ // display: flex;
+ // flex-wrap: wrap;
+ .content {
+ padding: 20rpx;
+ // width: calc(100% - 345rpx);
+ .text-cut {
+ font-size: 16px;
+ }
+ }
+ .image {
+ width: 100%;
+ height: 240rpx;
+ border-radius: 6rpx 0 0 6rpx;
+ display: block;
+ }
+ .ui-progress-tag {
+ width: 4em;
+ text-align: right;
+ font-size: 12px;
+ }
+ .border-top {
+ width: 100%;
+ }
+ .ui-tag {
+ position: absolute;
+ top: 0;
+ left: 0;
+ border-radius: 6rpx 0 6rpx 0;
+ }
+ }
+ // & > .ui-item:nth-child(2n) {
+ // margin-left: 0rpx;
+ // }
+ }
+ &.shop {
+ display: flex;
+ flex-wrap: wrap;
+ padding-bottom: 30rpx;
+ & > .ui-item {
+ width: calc(50% - 30rpx);
+ margin: 20rpx 20rpx 0rpx 20rpx;
+ background-color: var(--box-bg);
+ padding: 20rpx;
+ .content {
+ margin-top: 15rpx;
+ }
+ .image {
+ width: 100%;
+ height: 285rpx;
+ border-radius: 6rpx;
+ display: block;
+ }
+ }
+ & > .ui-item:nth-child(2n) {
+ margin-left: 0rpx;
+ }
+ }
+
+ &.orders .ui-item {
+ margin-top: 30rpx;
+ .address-box {
+ padding: 15rpx;
+ margin: 0 30rpx 30rpx;
+ border: 1px solid;
+ border-color: var(--main-a);
+ border-radius: 10px;
+ position: relative;
+ .ui-form-group {
+ min-height: 10px;
+ }
+ }
+ }
+}
diff --git a/sheep/scss/style/_code.scss b/sheep/scss/style/_code.scss
new file mode 100644
index 00000000..5221c88c
--- /dev/null
+++ b/sheep/scss/style/_code.scss
@@ -0,0 +1,55 @@
+.ui-code {
+ font-family: Monaco, Menlo, Consolas, 'Courier New';
+ font-size: 90%;
+ position: relative;
+ z-index: 1;
+ color: var(--ui-TC);
+ .ui-rich-text {
+ display: inline-block;
+ }
+
+ &.code {
+ display: inline-block;
+ padding: 0 10rpx;
+ margin: 0 10rpx;
+ border-radius: $radius-sm;
+ line-height: 1.6;
+ vertical-align: baseline;
+ }
+
+ &.pre {
+ display: block;
+ margin: 1em 0;
+ line-height: 1.6;
+ &.hasTitle {
+ margin: 3.2em 0 1em;
+ }
+ // border-radius: $radius-sm;
+ .ui-code-title {
+ position: absolute;
+ top: -2.2em;
+ color: var(--ui-TC-2);
+ left: 0;
+ }
+ .ui-rich-text {
+ padding: 40rpx;
+ white-space: pre-wrap;
+ word-break: break-all;
+ word-wrap: break-word;
+ }
+ .ui-scroll-view {
+ &.ui-scroll {
+ max-height: 500px;
+ white-space: pre;
+ }
+ }
+ .ui-copy-btn {
+ position: absolute;
+ z-index: 2;
+ top: 0;
+ right: 0;
+ padding: 0.8em;
+ border-radius: 0 $radius-sm 0 $radius-sm;
+ }
+ }
+}
diff --git a/sheep/scss/style/_flex.scss b/sheep/scss/style/_flex.scss
new file mode 100644
index 00000000..1daa45b8
--- /dev/null
+++ b/sheep/scss/style/_flex.scss
@@ -0,0 +1,79 @@
+/* ==================
+ 弹性布局
+ ==================== */
+.flex {
+ display: flex !important;
+ &-sub {
+ flex: 1 !important;
+ }
+ &-twice {
+ flex: 2 !important;
+ }
+ &-treble {
+ flex: 3 !important;
+ }
+ &-column {
+ flex-direction: column !important;
+ }
+ &-row {
+ flex-direction: row !important;
+ }
+ &-column-reverse {
+ flex-direction: column-reverse !important;
+ }
+ &-row-reverse {
+ flex-direction: row-reverse !important;
+ }
+ &-wrap {
+ flex-wrap: wrap !important;
+ }
+ &-center {
+ @include flex-center;
+ }
+ &-bar {
+ @include flex-bar;
+ }
+}
+.basis {
+ @each $class, $value in (xs: 20%, sm: 40%, df: 50%, lg: 60%, xl: 80%) {
+ &-#{$class} {
+ flex-basis: $value !important;
+ }
+ }
+}
+.align {
+ @each $class,
+ $value
+ in (start: flex-start, end: flex-end, center: center, stretch: stretch, baseline: baseline)
+ {
+ &-#{$class} {
+ align-items: $value !important;
+ }
+ }
+}
+.self {
+ @each $class,
+ $value
+ in (start: flex-start, end: flex-end, center: center, stretch: stretch, baseline: baseline)
+ {
+ &-#{$class} {
+ align-self: $value !important;
+ }
+ }
+}
+.justify {
+ @each $class,
+ $value
+ in (
+ start: flex-start,
+ end: flex-end,
+ center: center,
+ between: space-between,
+ around: space-around
+ )
+ {
+ &-#{$class} {
+ justify-content: $value !important;
+ }
+ }
+}
diff --git a/sheep/scss/style/_form.scss b/sheep/scss/style/_form.scss
new file mode 100644
index 00000000..91d3eb39
--- /dev/null
+++ b/sheep/scss/style/_form.scss
@@ -0,0 +1,121 @@
+/* ==================
+ 表单
+ ==================== */
+.ui-form-item {
+ padding: 1rpx 24rpx;
+ display: flex;
+ align-items: center;
+ min-height: 100rpx;
+ justify-content: space-between;
+ .title {
+ text-align: justify;
+ padding-right: 30rpx;
+ font-size: 30rpx;
+ position: relative;
+ height: 60rpx;
+ line-height: 60rpx;
+ }
+ .content {
+ flex: 1;
+ }
+ input,
+ ui-input {
+ flex: 1;
+ font-size: 30rpx;
+ color: #555;
+ padding-right: 20rpx;
+ }
+ text[class*='icon-'] {
+ font-size: 36rpx;
+ padding: 0;
+ box-sizing: border-box;
+ }
+ textarea {
+ margin: 32rpx 0 30rpx;
+ height: 4.6em;
+ width: 100%;
+ line-height: 1.2em;
+ flex: 1;
+ font-size: 28rpx;
+ padding: 0;
+ }
+ picker,
+ .arrow {
+ flex: 1;
+ padding-right: 40rpx;
+ overflow: hidden;
+ position: relative;
+ }
+ picker .picker,
+ .arrow > view {
+ line-height: 100rpx;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ width: 100%;
+ }
+ picker::after,
+ .arrow::after {
+ font-family: 'ui';
+ display: block;
+ content: '\e605';
+ position: absolute;
+ font-size: 34rpx;
+ color: #8799a3;
+ line-height: 100rpx;
+ width: 60rpx;
+ text-align: center;
+ top: 0;
+ bottom: 0;
+ right: -20rpx;
+ margin: auto;
+ }
+ textarea[disabled],
+ textarea[disabled] .placeholder {
+ color: transparent;
+ }
+ &.align-start .title {
+ height: 1em;
+ margin-top: 32rpx;
+ line-height: 1em;
+ }
+ .grid-square {
+ > view {
+ background-color: #f8f8f8;
+ border-radius: 12rpx;
+ .mask {
+ background-color: rgba(0, 0, 0, 0.6);
+ position: absolute;
+ font-size: 20rpx;
+ color: #ffffff;
+ width: 100%;
+ bottom: 0;
+ text-align: center;
+ padding: 6rpx 0;
+ &.red-mask {
+ background-color: rgba(255, 80, 80, 0.6);
+ }
+ }
+ [class*='icon'] {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ transform: scale(1.5);
+ justify-content: center;
+ }
+ .text-gray {
+ position: absolute;
+ width: 100%;
+ font-size: 24rpx;
+ text-align: center;
+ bottom: 20rpx;
+ }
+ }
+ }
+}
+.disabled {
+ opacity: 0.6;
+ cursor: not-allowed !important;
+}
diff --git a/sheep/scss/style/_grid.scss b/sheep/scss/style/_grid.scss
new file mode 100644
index 00000000..b1b52307
--- /dev/null
+++ b/sheep/scss/style/_grid.scss
@@ -0,0 +1,103 @@
+/* ==================
+ 栅栏
+ ==================== */
+@use 'sass:math';
+
+@mixin make_col($screen) {
+ @for $i from 1 through 12 {
+ .ui-col-#{$screen}-#{$i} {
+ width: calc(100% / 12 * #{$i});
+ }
+ .ui-cols-#{$screen}-#{$i} .ui-item {
+ width: calc(100% / #{$i});
+ }
+ }
+}
+.ui-container {
+ box-sizing: border-box;
+ margin-left: auto;
+ margin-right: auto;
+ padding-left: 30rpx;
+ padding-right: 30rpx;
+ width: 100%;
+ max-width: 1440px;
+ &-fluid {
+ max-width: 100%;
+ padding-left: 0;
+ padding-right: 0;
+ }
+}
+.ui-grid {
+ display: flex;
+ flex-wrap: wrap;
+ &.multi-column {
+ display: block;
+ column-count: 2;
+ column-width: 0px;
+ column-gap: 0px;
+ > .ui-item {
+ break-inside: avoid;
+ padding: 0.001em;
+ }
+ }
+ &.grid-square {
+ overflow: hidden;
+ > .ui-item {
+ margin-right: 20rpx;
+ margin-bottom: 20rpx;
+ position: relative;
+ overflow: hidden;
+ }
+ @for $i from 1 through 12 {
+ &.ui-cols-#{$i} > .ui-item {
+ padding-bottom: calc((100% - #{20rpx * ($i - 1)}) / #{$i});
+ height: 0;
+ width: calc((100% - #{20rpx * ($i - 1)}) / #{$i});
+ }
+ }
+ @for $i from 1 through 12 {
+ &.ui-cols-#{$i} > .ui-item:nth-child(#{$i}n) {
+ margin-right: 0;
+ }
+ }
+ }
+}
+@for $i from 1 through 12 {
+ .ui-cols-#{$i} .ui-item {
+ width: calc(100% / #{$i});
+ }
+}
+@for $i from 1 through 12 {
+ .ui-col-#{$i} {
+ width: calc(100% / 12 * #{$i});
+ }
+}
+// 小屏
+@media screen and (min-width: 0px) {
+ @include make_col('xs');
+}
+
+// 小屏
+@media screen and (min-width: 320px) {
+ @include make_col('sm');
+}
+
+// 中屏
+@media screen and (min-width: 768px) {
+ @include make_col('md');
+}
+
+// 普通屏
+@media screen and (min-width: 1025px) {
+ @include make_col('lg');
+}
+
+// 大屏
+@media screen and (min-width: 1440px) {
+ @include make_col('xl');
+}
+
+// 超大屏
+@media screen and (min-width: 1920px) {
+ @include make_col('xxl');
+}
diff --git a/sheep/scss/style/_markdown.scss b/sheep/scss/style/_markdown.scss
new file mode 100644
index 00000000..37b023d7
--- /dev/null
+++ b/sheep/scss/style/_markdown.scss
@@ -0,0 +1,62 @@
+.cu-markdown {
+ position: relative;
+ z-index: 1;
+ &.selectable {
+ cursor: auto;
+ user-select: text;
+ }
+ inline {
+ display: inline-block;
+ }
+
+ .list {
+ .list-item {
+ line-height: 1.8;
+ .list {
+ margin-left: 1.28571em;
+ .ui-title {
+ transform: scale(0.6);
+ &:before {
+ content: '\e716';
+ }
+ }
+ }
+ }
+ .list-item-p {
+ position: relative;
+ padding-left: 1.5em;
+ .list-item-t {
+ display: block;
+ width: 1.3em;
+ text-align: center;
+ position: absolute;
+ left: 0;
+ }
+ }
+ }
+ .md-table + .md-table {
+ margin-top: 30rpx;
+ }
+}
+
+.paragraph {
+ margin: 0 0 40rpx;
+ line-height: 1.8;
+}
+
+.blockquote {
+ @extend .paragraph;
+ padding: 20rpx 30rpx;
+ border-left-style: solid;
+ border-left-width: 10rpx;
+ border-color: var(--ui-Border);
+ background: none repeat scroll 0 0 rgba(102, 128, 153, 0.05);
+
+ .paragraph {
+ margin-bottom: 30rpx;
+ }
+
+ .paragraph:last-child {
+ margin-bottom: 0;
+ }
+}
diff --git a/sheep/scss/style/_menu.scss b/sheep/scss/style/_menu.scss
new file mode 100644
index 00000000..a4a82828
--- /dev/null
+++ b/sheep/scss/style/_menu.scss
@@ -0,0 +1,54 @@
+.ui-menu {
+ background-color: var(--ui-BG);
+}
+
+.ui-menu-item {
+ position: relative;
+ @include flex-bar;
+ min-height: 4em;
+ padding: 0 30rpx;
+ .ui-menu-item-icon {
+ width: 1.7em;
+ margin-right: 0.3em;
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transform: scale(1.3);
+ }
+ .ui-menu-item-icon .ui-menu-item-image {
+ width: 1.2em;
+ height: 1.2em;
+ display: inline-block;
+ }
+ .ui-menu-item-content {
+ flex: 1;
+ position: relative;
+ @include flex-bar;
+ }
+ .ui-menu-item-arrow {
+ width: 1.6em;
+ text-align: center;
+ color: var(--ui-TC-3);
+ }
+ &::after {
+ content: ' ';
+ width: calc(200% - 120rpx);
+ left: 30rpx;
+ position: absolute;
+ top: 0;
+ box-sizing: border-box;
+ height: 200%;
+ border-top: 1px solid var(--ui-Border);
+ border-radius: inherit;
+ transform: scale(1);
+ transform-origin: 0 0;
+ pointer-events: none;
+ }
+ &.first-item::after {
+ display: none;
+ }
+ &:first-child::after {
+ display: none;
+ }
+}
diff --git a/sheep/scss/style/_shadow.scss b/sheep/scss/style/_shadow.scss
new file mode 100644
index 00000000..27cb3f63
--- /dev/null
+++ b/sheep/scss/style/_shadow.scss
@@ -0,0 +1,90 @@
+/* ==================
+ 阴影
+ ==================== */
+
+.shadow {
+ box-shadow: var(--ui-Shadow);
+ &-sm {
+ box-shadow: var(--ui-Shadow-sm);
+ }
+ &-lg {
+ box-shadow: var(--ui-Shadow-lg);
+ }
+ &-inset {
+ box-shadow: var(--ui-Shadow-inset);
+ }
+ @each $color, $value in $colors {
+ @at-root .shadow-#{$color} {
+ box-shadow: 0 0.5em 1em rgba($value, var(--ui-Shadow-opacity));
+ }
+ &-sm.shadow-#{$color} {
+ box-shadow: 0 0.125em 0.25em rgba($value, var(--ui-Shadow-opacity));
+ }
+ &-lg.shadow-#{$color} {
+ box-shadow: 0 1em 3em rgba($value, var(--ui-Shadow-opacity-lg));
+ }
+ }
+
+ &-warp {
+ position: relative;
+ }
+ &-warp:before,
+ &-warp:after {
+ position: absolute;
+ content: '';
+ bottom: -10rpx;
+ left: 20rpx;
+ width: calc(50% - #{40rpx});
+ height: 30rpx;
+ transform: skew(0deg, -6deg);
+ transform-origin: 50% 50%;
+ background-color: rgba(0, 0, 0, var(--ui-Shadow-opacity));
+ filter: blur(20rpx);
+ z-index: -1;
+ opacity: 0.5;
+ }
+ &-warp:after {
+ right: 20rpx;
+ left: auto;
+ transform: skew(0deg, 6deg);
+ }
+ &-blur {
+ position: relative;
+ }
+ &-blur::before {
+ content: '';
+ display: block;
+ background: inherit;
+ filter: blur(20rpx);
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0.5em;
+ left: 0.5em;
+ z-index: -1;
+ opacity: var(--ui-Shadow-opacity-lg);
+ transform-origin: 0 0;
+ border-radius: inherit;
+ transform: scale(1, 1);
+ }
+}
+.drop-shadow {
+ filter: drop-shadow(0 0 30rpx rgba(0, 0, 0, 0.1));
+ &-sm {
+ filter: drop-shadow(0 4rpx 4rpx rgba(0, 0, 0, 0.06));
+ }
+ &-lg {
+ filter: drop-shadow(0 30rpx 60rpx rgba(0, 0, 0, 0.2));
+ }
+ @each $color, $value in $colors {
+ @at-root .drop-shadow-#{$color} {
+ filter: drop-shadow(0 15rpx 15rpx rgba(darken($value, 10%), 0.3));
+ }
+ &-sm.drop-shadow-#{$color} {
+ filter: drop-shadow(0 4rpx 4rpx rgba(darken($value, 10%), 0.3));
+ }
+ &-lg.drop-shadow-#{$color} {
+ filter: drop-shadow(0 50rpx 100rpx rgba(darken($value, 10%), 0.2));
+ }
+ }
+}
diff --git a/sheep/scss/style/_table.scss b/sheep/scss/style/_table.scss
new file mode 100644
index 00000000..ad5effa0
--- /dev/null
+++ b/sheep/scss/style/_table.scss
@@ -0,0 +1,133 @@
+.ui-table {
+ background-color: var(--ui-BG);
+ max-width: 100%;
+ display: table;
+ &.table-full {
+ width: 100%;
+ }
+ &.table-radius {
+ border-radius: $radius;
+ .ui-table-header {
+ .ui-table-tr {
+ border-top-left-radius: $radius;
+ border-top-right-radius: $radius;
+ }
+ .ui-table-th {
+ &:first-child {
+ border-top-left-radius: $radius;
+ }
+ &:last-child {
+ border-top-right-radius: $radius;
+ }
+ }
+ }
+ }
+ .ui-table-header {
+ display: table-header-group;
+ .ui-table-th {
+ font-weight: bold;
+ border-bottom: 1px solid var(--ui-Border);
+ white-space: nowrap;
+
+ padding: 1em 0.8em;
+ }
+ }
+
+ .ui-table-tr {
+ display: table-row;
+ z-index: 1;
+ }
+
+ .ui-table-body {
+ display: table-row-group;
+ position: relative;
+ .ui-table-tr:hover {
+ background-color: var(--ui-BG-1) !important;
+ }
+ .ui-table-loading {
+ min-height: 300px;
+ position: absolute !important;
+ width: 100%;
+ height: 100%;
+ z-index: 2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid var(--ui-Border);
+ }
+ }
+
+ .ui-table-td,
+ .ui-table-th {
+ display: table-cell;
+ text-align: unset;
+ padding: 0.5em 0.8em;
+ // font-size: 90%;
+ vertical-align: middle;
+ }
+}
+
+.ui-table.table-border {
+ &,
+ & .ui-table-td,
+ & .ui-table-th {
+ position: relative;
+ &::after {
+ content: ' ';
+ width: 200%;
+ height: 200%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ border-radius: inherit;
+ transform: scale(0.5);
+ transform-origin: 0 0;
+ pointer-events: none;
+ box-sizing: border-box;
+ border: 1px solid var(--ui-Border);
+ z-index: 1;
+ }
+ }
+ .ui-table-td,
+ .ui-table-th {
+ &::after {
+ border-width: 1px 1px 0 0;
+ }
+ &:last-child::after {
+ border-right: none;
+ }
+ }
+}
+.ui-table.table-radius {
+ &::after {
+ border-radius: calc(#{$radius} * 2);
+ }
+ & .ui-table-tr .ui-table-th:first-child {
+ border-top-left-radius: calc(#{$radius} * 2);
+ }
+ & .ui-table-tr .ui-table-th:last-child {
+ border-top-right-radius: calc(#{$radius} * 2);
+ }
+ & .ui-table-tr:last-child .ui-table-td:first-child {
+ border-bottom-left-radius: #{$radius};
+ }
+ & .ui-table-tr:last-child .ui-table-td:last-child {
+ border-bottom-right-radius: #{$radius};
+ }
+}
+.ui-table.table-striped > .ui-table-body > .ui-table-tr:nth-child(2n + 1),
+.ui-table.table-striped > .ui-table-body > .ui-table-tr:nth-child(2n + 1) {
+ background-color: var(--ui-BG-1);
+}
+
+.table-responsive {
+ width: inherit;
+ height: 100%;
+ max-width: 100%;
+ overflow: hidden;
+ box-sizing: border-box;
+ .table-responsive-box {
+ position: relative;
+ overflow: hidden;
+ }
+}
diff --git a/sheep/scss/style/_tag.scss b/sheep/scss/style/_tag.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/sheep/scss/style/_text.scss b/sheep/scss/style/_text.scss
new file mode 100644
index 00000000..82490221
--- /dev/null
+++ b/sheep/scss/style/_text.scss
@@ -0,0 +1,104 @@
+/* ==================
+ 文本
+ ==================== */
+@use 'sass:math';
+.font-0 {
+ font-size: 24rpx;
+ --textSize: -4rpx;
+}
+.font-1 {
+ font-size: 28rpx;
+ --textSize: 0rpx;
+}
+.font-2 {
+ font-size: 32rpx;
+ --textSize: 4rpx;
+}
+.font-3 {
+ font-size: 36rpx;
+ --textSize: 8rpx;
+}
+.font-4 {
+ font-size: 40rpx;
+ --textSize: 12rpx;
+}
+.text {
+ @each $class, $value in $fontsize {
+ &-#{$class},
+ &-#{math.div($value ,2)} {
+ font-size: calc(#{$value}rpx + var(--textSize)) !important;
+ }
+ }
+ &-cut {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+ @at-root [class*='text-linecut'] {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ word-break: break-all;
+ }
+ @for $i from 2 through 10 {
+ &-linecut-#{$i} {
+ -webkit-line-clamp: #{$i};
+ }
+ }
+ &-justify {
+ text-align: justify;
+ }
+ &-justify-line {
+ text-align: justify;
+ line-height: 0.5em;
+ margin-top: 0.5em;
+ &::after {
+ content: '.';
+ display: inline-block;
+ width: 100%;
+ }
+ }
+
+ &-Abc {
+ text-transform: Capitalize !important;
+ }
+ &-ABC {
+ text-transform: Uppercase !important;
+ }
+ &-abc {
+ text-transform: Lowercase !important;
+ }
+ &-del,
+ &-line {
+ text-decoration: line-through !important;
+ }
+ &-bottomline {
+ text-decoration: underline !important;
+ }
+ &-italic {
+ font-style: italic !important;
+ }
+ &-style-none {
+ text-decoration: none !important;
+ }
+ &-break {
+ word-break: break-word !important;
+ overflow-wrap: break-word !important;
+ }
+ &-reset {
+ color: inherit !important;
+ }
+ &-price::before {
+ content: '¥';
+ font-size: 80%;
+ margin-right: 4rpx;
+ }
+ &-hide {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+ }
+}
diff --git a/sheep/scss/theme/_dark.scss b/sheep/scss/theme/_dark.scss
new file mode 100644
index 00000000..8caad174
--- /dev/null
+++ b/sheep/scss/theme/_dark.scss
@@ -0,0 +1,39 @@
+// 核心主题样式文件
+@mixin theme-dark {
+ // 背景色
+ --ui-BG: #393939;
+ --ui-BG-1: #333333;
+ --ui-BG-2: #2c2c2c;
+ --ui-BG-3: #292929;
+ --ui-BG-4: #222222;
+
+ // 文本色
+ --ui-TC: #ffffff;
+ --ui-TC-1: #d4d4d4;
+ --ui-TC-2: #919191;
+ --ui-TC-3: #6a6a6a;
+ --ui-TC-4: #474747;
+
+ // 模糊
+ --ui-Blur: rgba(38, 38, 38, 0.98);
+ --ui-Blur-1: rgba(38, 38, 38, 0.75);
+ --ui-Blur-2: rgba(38, 38, 38, 0.25);
+ --ui-Blur-3: rgba(38, 38, 38, 0.05);
+
+ // 边框
+ --ui-Border: rgba(119, 119, 119, 0.25);
+ --ui-Outline: rgba(255, 255, 255, 0.1);
+ --ui-Line: rgba(119, 119, 119, 0.25);
+
+ // 透明与阴影
+ --ui-Shadow: 0 0.5em 1em rgba(0, 0, 0, 0.45);
+ --ui-Shadow-sm: 0 0.125em 0.25em rgba(0, 0, 0, 0.475);
+ --ui-Shadow-lg: 0 1em 3em rgba(0, 0, 0, 0.475);
+ --ui-Shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.475);
+
+ --ui-Shadow-opacity: 0.55;
+ --ui-Shadow-opacity-sm: 0.175;
+ --ui-Shadow-opacity-lg: 0.75;
+
+ --ui-BG-opacity: 0.1;
+}
diff --git a/sheep/scss/theme/_light.scss b/sheep/scss/theme/_light.scss
new file mode 100644
index 00000000..af5f245a
--- /dev/null
+++ b/sheep/scss/theme/_light.scss
@@ -0,0 +1,39 @@
+// 核心主题样式文件
+@mixin theme-light {
+ // 背景色
+ --ui-BG: #ffffff;
+ --ui-BG-1: #f6f6f6;
+ --ui-BG-2: #f1f1f1;
+ --ui-BG-3: #e8e8e8;
+ --ui-BG-4: #e0e0e0;
+
+ // 文本色
+ --ui-TC: #303030;
+ --ui-TC-1: #525252;
+ --ui-TC-2: #777777;
+ --ui-TC-3: #9e9e9e;
+ --ui-TC-4: #c6c6c6;
+
+ // 模糊
+ --ui-Blur: rgba(255, 255, 255, 0.98);
+ --ui-Blur-1: rgba(255, 255, 255, 0.75);
+ --ui-Blur-2: rgba(255, 255, 255, 0.25);
+ --ui-Blur-3: rgba(255, 255, 255, 0.05);
+
+ // 边框
+ --ui-Border: rgba(119, 119, 119, 0.25);
+ --ui-Outline: rgba(0, 0, 0, 0.1);
+ --ui-Line: rgba(119, 119, 119, 0.25);
+
+ // 透明与阴影
+ --ui-Shadow: 0 0.5em 1em rgba(0, 0, 0, 0.15);
+ --ui-Shadow-sm: 0 0.125em 0.25em rgba(0, 0, 0, 0.075);
+ --ui-Shadow-lg: 0 1em 3em rgba(0, 0, 0, 0.175);
+ --ui-Shadow-inset: inset 0 0.1em 0.2em rgba(0, 0, 0, 0.075);
+
+ --ui-Shadow-opacity: 0.45;
+ --ui-Shadow-opacity-sm: 0.075;
+ --ui-Shadow-opacity-lg: 0.65;
+
+ --ui-BG-opacity: 0.1;
+}
diff --git a/sheep/scss/theme/_style.scss b/sheep/scss/theme/_style.scss
new file mode 100644
index 00000000..1eef5875
--- /dev/null
+++ b/sheep/scss/theme/_style.scss
@@ -0,0 +1,68 @@
+@import './light'; //浅蓝主题
+@import './dark'; //深蓝主题
+// 多主题
+.theme-light {
+ @include theme-light;
+}
+.theme-dark {
+ @include theme-dark;
+}
+.theme-auto {
+ @include theme-light;
+}
+@media (prefers-color-scheme: dark) {
+ .theme-auto {
+ @include theme-dark;
+ }
+}
+
+@each $value in ('', '-1', '-2', '-3', '-4') {
+ // 背景色 + 文字色 : 白色 + 默认色;
+ .ui-BG#{$value} {
+ background-color: var(--ui-BG#{$value}) !important;
+ color: var(--ui-TC);
+ }
+ // 文字颜色
+ .ui-TC#{$value} {
+ color: var(--ui-TC#{$value}) !important;
+ }
+ // 主题色背景
+ .ui-BG-Main#{$value} {
+ background-color: var(--ui-BG-Main#{$value}) !important;
+ color: var(--ui-BG-Main-TC) !important;
+ }
+ // 主题色渐变,横向
+ .ui-BG-Main-Gradient {
+ background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient)) !important;
+ color: var(--ui-BG-Main-TC) !important;
+ }
+ // 主题色文字
+ .ui-TC-Main#{$value} {
+ color: var(--ui-BG-Main#{$value}) !important;
+ }
+ // 主题色阴影
+ .ui-Shadow-Main {
+ box-shadow: var(--ui-Main-box-shadow) !important;
+ }
+ .ui-BG-Main-light {
+ background: var(----ui-BG-Main-light) !important;
+ color: var(--ui-BG-Main#{$value}) !important;
+ }
+}
+
+@each $color, $value in $colors {
+ .main-#{$color} {
+ --ui-BG-Main: #{$value};
+ --ui-BG-Main-tag: #{rgba($value, 0.05)};
+ --ui-BG-Main-gradient: #{rgba($value, 0.6)};
+ --ui-BG-Main-light: #{rgba($value, 0.2)};
+ --ui-BG-Main-opacity-1: #{rgba($value, 0.1)};
+ --ui-BG-Main-opacity-4: #{rgba($value, 0.4)};
+ --ui-Main-box-shadow: 0 0.2em 0.5em #{rgba($value, var(--ui-Shadow-opacity))};
+ --ui-BG-Main-1: #{mix(rgba(255, 255, 255, 0.7), desaturate($value, 20%), 10%)};
+ --ui-BG-Main-2: #{mix(rgba(255, 255, 255, 0.6), desaturate($value, 40%), 20%)};
+ --ui-BG-Main-3: #{mix(rgba(119, 119, 119, 0.2), desaturate($value, 40%), 40%)};
+ --ui-BG-Main-4: #{mix(rgba(119, 119, 119, 0.1), desaturate($value, 40%), 60%)};
+ --ui-BG-Main-TC: #ffffff !important;
+ }
+}
diff --git a/sheep/scss/ui.scss b/sheep/scss/ui.scss
new file mode 100644
index 00000000..b9b73811
--- /dev/null
+++ b/sheep/scss/ui.scss
@@ -0,0 +1,19 @@
+@import './theme/style'; //系统主题
+@import './main'; //主样式*
+
+@import './style/background'; //背景
+@import './style/grid'; //列
+@import './style/flex'; //布局
+@import './style/border'; //边框
+@import './style/text'; //文本
+@import './style/shadow'; //阴影
+@import './icon/style'; //图标
+@import './style/tag'; //标签
+@import './style/button'; //按钮
+@import './style/avatar'; //头像
+@import './style/table'; //表格
+@import './style/code'; //代码片段
+@import './style/form'; //表单
+@import './style/menu'; //表单
+@import './style/markdown'; //表单
+@import './style/card'; //表单
diff --git a/sheep/store/app.js b/sheep/store/app.js
new file mode 100644
index 00000000..fe9fc20f
--- /dev/null
+++ b/sheep/store/app.js
@@ -0,0 +1,96 @@
+import appApi from '@/sheep/api/app';
+import { defineStore } from 'pinia';
+import $platform from '@/sheep/platform';
+import $router from '@/sheep/router';
+import user from './user';
+import sys from './sys';
+
+const app = defineStore({
+ id: 'app',
+ state: () => ({
+ info: {
+ // 应用信息
+ name: '', // 商城名称
+ logo: '', // logo
+ version: '', // 版本号
+ cdnurl: '', // 云存储域名
+ filesystem: '', // 云存储平台
+ user_protocol: {}, // 用户协议
+ privacy_protocol: {}, // 隐私协议
+ about_us: {}, // 关于我们
+ copyright: '', // 版权信息 I
+ copytime: '', // 版权信息 II
+ },
+ platform: {
+ payment: [], // 支持的支付方式
+ share: {
+ methods: [], // 支持的分享方式
+ forwardInfo: {}, // 默认转发信息
+ posterInfo: {}, // 海报信息
+ linkAddress: '', // 复制链接地址
+ },
+ auto_login: 0, // 自动登陆
+ bind_mobile: 0, // 登陆后绑定手机号提醒 (弱提醒,可手动关闭)
+ },
+ chat: {},
+ template: {
+ // 店铺装修模板
+ basic: {}, // 基本信息
+ home: {
+ // 首页模板
+ style: {},
+ data: [],
+ },
+ user: {
+ // 个人中心模板
+ style: {},
+ data: [],
+ },
+ },
+ shareInfo: {}, // 全局分享信息
+ }),
+ actions: {
+ // 获取Shopro应用配置和模板
+ async init(templateId = null) {
+ //检查网络
+ const networkStatus = await $platform.checkNetwork();
+ if (!networkStatus) {
+ $router.error('NetworkError');
+ }
+
+ const res = await appApi.init(templateId);
+ if (res.error === 0) {
+ this.info = res.data.app;
+ this.platform = res.data.platform;
+ this.template = res.data.template;
+ if (!res.data.template) {
+ $router.error('TemplateError');
+ }
+ this.chat = res.data.chat;
+
+ // 加载主题
+ const sysStore = sys();
+ sysStore.setTheme();
+
+ // 模拟用户登录
+ const userStore = user();
+ if (userStore.isLogin) {
+ userStore.loginAfter();
+ }
+ return Promise.resolve(true);
+ } else {
+ $router.error('InitError', res.msg || '加载失败');
+ }
+ },
+ },
+ persist: {
+ enabled: true,
+ strategies: [
+ {
+ key: 'app-store',
+ },
+ ],
+ },
+});
+
+export default app;
diff --git a/sheep/store/cart.js b/sheep/store/cart.js
new file mode 100644
index 00000000..829dd088
--- /dev/null
+++ b/sheep/store/cart.js
@@ -0,0 +1,108 @@
+import { defineStore } from 'pinia';
+import cartApi from '@/sheep/api/cart';
+
+const cart = defineStore({
+ id: 'cart',
+ state: () => ({
+ list: [], // 购物车列表
+ selectedIds: [], // 已选列表
+ isAllSelected: false, //是否全选
+ cartSelectedTotalPrice: '0.00', // 选中项总金额
+ }),
+ getters: {
+ totalPriceSelected: (state) => {
+ let price = 0;
+ if (!state.selectedIds.length) return price.toFixed(2);
+ state.list.forEach((item) => {
+ price += state.selectedIds.includes(item.id)
+ ? Number(item.sku_price.price) * item.goods_num
+ : 0;
+ });
+ return price.toFixed(2);
+ },
+ },
+ actions: {
+ // 获取购物车列表
+ async getList() {
+ const { data, error } = await cartApi.list();
+ if (error === 0) {
+ this.list = data;
+ }
+ },
+ // 添加购物车
+ async add(goodsInfo) {
+ const { error } = await cartApi.append({
+ goods_id: goodsInfo.goods_id,
+ goods_num: goodsInfo.goods_num,
+ goods_sku_price_id: goodsInfo.id,
+ });
+ if (error === 0) {
+ this.getList();
+ }
+ },
+
+ // 更新购物车
+ async update(goodsInfo) {
+ const { error } = await cartApi.update({
+ goods_id: goodsInfo.goods_id,
+ goods_num: goodsInfo.goods_num,
+ goods_sku_price_id: goodsInfo.goods_sku_price_id,
+ });
+ if (error === 0) {
+ // this.getList();
+ }
+ },
+
+ // 移除购物车
+ async delete(ids) {
+ if (typeof ids === 'array') {
+ ids = ids.join(',');
+ }
+ const { error } = await cartApi.delete(ids);
+ if (error === 0) {
+ this.selectAll(false);
+ this.getList();
+ }
+ },
+
+ // 选择购物车商品
+ selectSingle(goodsId) {
+ if (!this.selectedIds.includes(goodsId)) {
+ this.selectedIds.push(goodsId);
+ } else {
+ this.selectedIds.splice(this.selectedIds.indexOf(goodsId), 1);
+ }
+ this.isAllSelected = this.selectedIds.length === this.list.length;
+ },
+
+ // 全选
+ selectAll(flag) {
+ this.isAllSelected = flag;
+ if (!flag) {
+ this.selectedIds = [];
+ } else {
+ this.list.forEach((item) => {
+ this.selectedIds.push(item.id);
+ });
+ }
+ },
+
+ // 清空购物车
+ emptyList() {
+ this.list = [];
+ this.selectedIds = [];
+ this.isAllSelected = false;
+ this.cartSelectedTotalPrice = '0.00';
+ },
+ },
+ persist: {
+ enabled: true,
+ strategies: [
+ {
+ key: 'cart-store',
+ },
+ ],
+ },
+});
+
+export default cart;
diff --git a/sheep/store/index.js b/sheep/store/index.js
new file mode 100644
index 00000000..db59bd48
--- /dev/null
+++ b/sheep/store/index.js
@@ -0,0 +1,20 @@
+import { createPinia } from 'pinia';
+import piniaPersist from 'pinia-plugin-persist-uni';
+
+// 自动注入所有pinia模块
+const files = import.meta.globEager('./*.js');
+const modules = {};
+Object.keys(files).forEach((key) => {
+ modules[key.replace(/(.*\/)*([^.]+).*/gi, '$2')] = files[key].default;
+});
+
+export const setupPinia = (app) => {
+ const pinia = createPinia();
+ pinia.use(piniaPersist);
+
+ app.use(pinia);
+};
+
+export default (name) => {
+ return modules[name]();
+};
diff --git a/sheep/store/modal.js b/sheep/store/modal.js
new file mode 100644
index 00000000..9e369522
--- /dev/null
+++ b/sheep/store/modal.js
@@ -0,0 +1,29 @@
+import { defineStore } from 'pinia';
+
+const modal = defineStore({
+ id: 'modal',
+ state: () => ({
+ auth: '', // 授权弹框 accountLogin|smsLogin|smsRegister|resetPassword|changeMobile|changePassword|changeUsername
+ share: false, // 分享弹框
+ menu: false, // 快捷菜单弹框
+ advHistory: [], // 广告弹框记录
+ lastTimer: {
+ // 短信验证码计时器,为了防止刷新请求做了持久化
+ smsLogin: 0,
+ smsRegister: 0,
+ changeMobile: 0,
+ resetPassword: 0,
+ }
+ }),
+ persist: {
+ enabled: true,
+ strategies: [
+ {
+ key: 'modal-store',
+ paths: ['lastTimer', 'advHistory'],
+ },
+ ],
+ },
+});
+
+export default modal;
diff --git a/sheep/store/sys.js b/sheep/store/sys.js
new file mode 100644
index 00000000..f7151e0a
--- /dev/null
+++ b/sheep/store/sys.js
@@ -0,0 +1,32 @@
+import { defineStore } from 'pinia';
+import app from './app';
+
+const sys = defineStore({
+ id: 'sys',
+ state: () => ({
+ theme: '', // 主题,
+ mode: 'light', // 明亮模式、暗黑模式(暂未支持)
+ modeAuto: false, // 跟随系统
+ fontSize: 1, // 设置默认字号等级(0-4)
+ }),
+ getters: {},
+ actions: {
+ setTheme(theme = '') {
+ if (theme === '') {
+ this.theme = app().template?.basic.theme || 'orange';
+ } else {
+ this.theme = theme;
+ }
+ },
+ },
+ persist: {
+ enabled: true,
+ strategies: [
+ {
+ key: 'sys-store',
+ },
+ ],
+ },
+});
+
+export default sys;
diff --git a/sheep/store/user.js b/sheep/store/user.js
new file mode 100644
index 00000000..889009be
--- /dev/null
+++ b/sheep/store/user.js
@@ -0,0 +1,158 @@
+import { defineStore } from 'pinia';
+import userApi from '@/sheep/api/user';
+import commissionApi from '@/sheep/api/commission';
+import $share from '@/sheep/platform/share';
+import { isEmpty, cloneDeep, clone } from 'lodash';
+import cart from './cart';
+import app from './app';
+import { showAuthModal } from '@/sheep/hooks/useModal';
+
+// 默认用户信息
+const defaultUserInfo = {
+ avatar: '', // 头像
+ nickname: '', // 昵称
+ gender: 0, // 性别
+ mobile: '', // 手机号
+ money: '--', // 余额
+ commission: '--', // 佣金
+ score: '--', // 积分
+ verification: {}, // 认证字段
+};
+
+// 默认订单、优惠券等其他资产信息
+const defaultNumData = {
+ coupons_num: '--',
+ order_num: {
+ aftersale: 0,
+ nocomment: 0,
+ noget: 0,
+ nosend: 0,
+ unpaid: 0,
+ },
+};
+
+const user = defineStore({
+ id: 'user',
+ state: () => ({
+ userInfo: clone(defaultUserInfo), // 用户信息
+ isLogin: !!uni.getStorageSync('token'), // 登录状态
+ numData: cloneDeep(defaultNumData), // 用户其他数据
+ agentInfo: {}, // 分销商信息
+ lastUpdateTime: 0, // 上次更新时间
+ }),
+
+ actions: {
+ // 获取个人信息
+ async getInfo() {
+ const { error, data } = await userApi.profile();
+ if (error !== 0) return;
+ this.userInfo = data;
+
+ return Promise.resolve(data);
+ },
+
+ // 获取分销商信息
+ async getAgentInfo() {
+ const res = await commissionApi.agent();
+ if (res.error === 0) {
+ this.agentInfo = res.data;
+ }
+ return Promise.resolve(res);
+ },
+
+ // 获取订单、优惠券等其他资产信息
+ async getNumData() {
+ const { error, data } = await userApi.data();
+ if (error === 0) {
+ this.numData = data;
+ }
+ },
+
+ // 添加分享记录
+ async addShareLog(params) {
+ const { error } = await userApi.addShareLog(params);
+ if (error === 0) uni.removeStorageSync('shareLog');
+ },
+
+ // 设置token
+ setToken(token = '') {
+ if (token === '') {
+ this.isLogin = false;
+ uni.removeStorageSync('token');
+ } else {
+ this.isLogin = true;
+ uni.setStorageSync('token', token);
+ this.loginAfter();
+ }
+ return this.isLogin;
+ },
+
+ // 更新用户相关信息 (手动限流 5秒之内不刷新)
+ async updateUserData() {
+ if (!this.isLogin) {
+ this.resetUserData();
+ return;
+ }
+ const nowTime = new Date().getTime();
+ if (this.lastUpdateTime + 5000 > nowTime) return;
+ await this.getInfo();
+ this.getNumData();
+ this.lastUpdateTime = nowTime;
+ return this.userInfo;
+ },
+
+ // 重置用户默认数据
+ resetUserData() {
+ this.setToken();
+ this.userInfo = clone(defaultUserInfo);
+ this.numData = cloneDeep(defaultNumData);
+ this.agentInfo = {};
+ cart().emptyList();
+ },
+
+ // 登录后
+ async loginAfter() {
+ await this.updateUserData();
+ cart().getList();
+ // 登录后设置全局分享参数
+ $share.getShareInfo();
+ // 提醒绑定手机号
+ if (app().platform.bind_mobile && !this.userInfo.verification?.mobile) {
+ showAuthModal('changeMobile');
+ }
+
+ // 添加分享记录
+ const shareLog = uni.getStorageSync('shareLog');
+ if (!isEmpty(shareLog)) {
+ this.addShareLog({
+ ...shareLog,
+ });
+ }
+ },
+
+ // 登出
+ async logout(force = false) {
+ if (!force) {
+ const { error } = await userApi.logout();
+ if (error === 0) {
+ this.resetUserData();
+ }
+ }
+ if (force) {
+ this.resetUserData();
+ }
+
+ return !this.isLogin;
+ },
+ },
+ persist: {
+ enabled: true,
+ strategies: [
+ {
+ key: 'user-store',
+ },
+ ],
+ },
+});
+
+export default user;
diff --git a/sheep/ui/su-coupon/su-coupon.vue b/sheep/ui/su-coupon/su-coupon.vue
new file mode 100644
index 00000000..61cb7a41
--- /dev/null
+++ b/sheep/ui/su-coupon/su-coupon.vue
@@ -0,0 +1,315 @@
+
+
+
+
+
+
+ {{ props.value }}
+ 元
+
+ {{ props.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+ ¥
+ {{ props.value }}
+
+
+ {{ props.title }}
+ 仅剩:{{ props.surplus }}张
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ¥
+ {{ props.value }}
+
+
+ {{ props.title }}
+ 有效期:{{ props.sellBy }}
+
+
+
+
+
+
+ 仅剩:{{ props.surplus }}张
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-data-checkbox/su-data-checkbox.vue b/sheep/ui/su-data-checkbox/su-data-checkbox.vue
new file mode 100644
index 00000000..537ead5f
--- /dev/null
+++ b/sheep/ui/su-data-checkbox/su-data-checkbox.vue
@@ -0,0 +1,894 @@
+
+
+
+
+
+ {{ mixinDatacomErrorMessage }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-dialog/su-dialog.vue b/sheep/ui/su-dialog/su-dialog.vue
new file mode 100644
index 00000000..b53e9ce1
--- /dev/null
+++ b/sheep/ui/su-dialog/su-dialog.vue
@@ -0,0 +1,269 @@
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-fixed/su-fixed.vue b/sheep/ui/su-fixed/su-fixed.vue
new file mode 100644
index 00000000..e2a98088
--- /dev/null
+++ b/sheep/ui/su-fixed/su-fixed.vue
@@ -0,0 +1,217 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-image/su-image.vue b/sheep/ui/su-image/su-image.vue
new file mode 100644
index 00000000..32603371
--- /dev/null
+++ b/sheep/ui/su-image/su-image.vue
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-inner-navbar/su-inner-navbar.vue b/sheep/ui/su-inner-navbar/su-inner-navbar.vue
new file mode 100644
index 00000000..9fc102da
--- /dev/null
+++ b/sheep/ui/su-inner-navbar/su-inner-navbar.vue
@@ -0,0 +1,365 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-navbar/su-navbar.vue b/sheep/ui/su-navbar/su-navbar.vue
new file mode 100644
index 00000000..d85e3fe1
--- /dev/null
+++ b/sheep/ui/su-navbar/su-navbar.vue
@@ -0,0 +1,477 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-notice-bar/su-notice-bar.vue b/sheep/ui/su-notice-bar/su-notice-bar.vue
new file mode 100644
index 00000000..448b442d
--- /dev/null
+++ b/sheep/ui/su-notice-bar/su-notice-bar.vue
@@ -0,0 +1,472 @@
+
+
+
+
+
+
+
+
+ {{ text }}
+
+
+
+
+
+ {{ moreText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-number-box/su-number-box.vue b/sheep/ui/su-number-box/su-number-box.vue
new file mode 100644
index 00000000..4f3d5924
--- /dev/null
+++ b/sheep/ui/su-number-box/su-number-box.vue
@@ -0,0 +1,290 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-parse/node.vue b/sheep/ui/su-parse/node.vue
new file mode 100644
index 00000000..59b9669b
--- /dev/null
+++ b/sheep/ui/su-parse/node.vue
@@ -0,0 +1,536 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{n.text}}
+
+ \n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sheep/ui/su-parse/node/node.vue b/sheep/ui/su-parse/node/node.vue
new file mode 100644
index 00000000..0b37169c
--- /dev/null
+++ b/sheep/ui/su-parse/node/node.vue
@@ -0,0 +1,619 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ n.text }}
+
+ \n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-parse/parser.js b/sheep/ui/su-parse/parser.js
new file mode 100644
index 00000000..e007e99d
--- /dev/null
+++ b/sheep/ui/su-parse/parser.js
@@ -0,0 +1,1260 @@
+/**
+ * @fileoverview html 解析器
+ */
+
+// 配置
+const config = {
+ // 信任的标签(保持标签名不变)
+ trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,ruby,rt,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
+
+ // 块级标签(转为 div,其他的非信任标签转为 span)
+ blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
+
+ // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
+ // 行内标签
+ inlineTags: makeMap('abbr,b,big,code,del,em,i,ins,label,q,small,span,strong,sub,sup'),
+ // #endif
+
+ // 要移除的标签
+ ignoreTags: makeMap('area,base,canvas,embed,frame,head,iframe,input,link,map,meta,param,rp,script,source,style,textarea,title,track,wbr'),
+
+ // 自闭合的标签
+ voidTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
+
+ // html 实体
+ entities: {
+ lt: '<',
+ gt: '>',
+ quot: '"',
+ apos: "'",
+ ensp: '\u2002',
+ emsp: '\u2003',
+ nbsp: '\xA0',
+ semi: ';',
+ ndash: '–',
+ mdash: '—',
+ middot: '·',
+ lsquo: '‘',
+ rsquo: '’',
+ ldquo: '“',
+ rdquo: '”',
+ bull: '•',
+ hellip: '…'
+ },
+
+ // 默认的标签样式
+ tagStyle: {
+ // #ifndef APP-PLUS-NVUE
+ address: 'font-style:italic',
+ big: 'display:inline;font-size:1.2em',
+ caption: 'display:table-caption;text-align:center',
+ center: 'text-align:center',
+ cite: 'font-style:italic',
+ dd: 'margin-left:40px',
+ mark: 'background-color:yellow',
+ pre: 'font-family:monospace;white-space:pre',
+ s: 'text-decoration:line-through',
+ small: 'display:inline;font-size:0.8em',
+ strike: 'text-decoration:line-through',
+ u: 'text-decoration:underline'
+ // #endif
+ },
+
+ // svg 大小写对照表
+ svgDict: {
+ animatetransform: 'animateTransform',
+ lineargradient: 'linearGradient',
+ viewbox: 'viewBox',
+ attributename: 'attributeName',
+ repeatcount: 'repeatCount',
+ repeatdur: 'repeatDur'
+ }
+}
+const tagSelector={}
+const {
+ windowWidth,
+ // #ifdef MP-WEIXIN
+ system
+ // #endif
+} = uni.getSystemInfoSync()
+const blankChar = makeMap(' ,\r,\n,\t,\f')
+let idIndex = 0
+
+// #ifdef H5 || APP-PLUS
+config.ignoreTags.iframe = undefined
+config.trustTags.iframe = true
+config.ignoreTags.embed = undefined
+config.trustTags.embed = true
+// #endif
+// #ifdef APP-PLUS-NVUE
+config.ignoreTags.source = undefined
+config.ignoreTags.style = undefined
+// #endif
+
+/**
+ * @description 创建 map
+ * @param {String} str 逗号分隔
+ */
+function makeMap (str) {
+ const map = Object.create(null)
+ const list = str.split(',')
+ for (let i = list.length; i--;) {
+ map[list[i]] = true
+ }
+ return map
+}
+
+/**
+ * @description 解码 html 实体
+ * @param {String} str 要解码的字符串
+ * @param {Boolean} amp 要不要解码 &
+ * @returns {String} 解码后的字符串
+ */
+function decodeEntity (str, amp) {
+ let i = str.indexOf('&')
+ while (i !== -1) {
+ const j = str.indexOf(';', i + 3)
+ let code
+ if (j === -1) break
+ if (str[i + 1] === '#') {
+ // { 形式的实体
+ code = parseInt((str[i + 2] === 'x' ? '0' : '') + str.substring(i + 2, j))
+ if (!isNaN(code)) {
+ str = str.substr(0, i) + String.fromCharCode(code) + str.substr(j + 1)
+ }
+ } else {
+ // 形式的实体
+ code = str.substring(i + 1, j)
+ if (config.entities[code] || (code === 'amp' && amp)) {
+ str = str.substr(0, i) + (config.entities[code] || '&') + str.substr(j + 1)
+ }
+ }
+ i = str.indexOf('&', i + 1)
+ }
+ return str
+}
+
+/**
+ * @description html 解析器
+ * @param {Object} vm 组件实例
+ */
+function Parser (vm) {
+ this.options = vm || {}
+ this.tagStyle = Object.assign({}, config.tagStyle, this.options.tagStyle)
+ this.imgList = vm.imgList || []
+ this.plugins = vm.plugins || []
+ this.attrs = Object.create(null)
+ this.stack = []
+ this.nodes = []
+ this.pre = (this.options.containerStyle || '').includes('white-space') && this.options.containerStyle.includes('pre') ? 2 : 0
+}
+
+/**
+ * @description 执行解析
+ * @param {String} content 要解析的文本
+ */
+Parser.prototype.parse = function (content) {
+ // 插件处理
+ for (let i = this.plugins.length; i--;) {
+ if (this.plugins[i].onUpdate) {
+ content = this.plugins[i].onUpdate(content, config) || content
+ }
+ }
+
+ new Lexer(this).parse(content)
+ // 出栈未闭合的标签
+ while (this.stack.length) {
+ this.popNode()
+ }
+ return this.nodes
+}
+
+/**
+ * @description 将标签暴露出来(不被 rich-text 包含)
+ */
+Parser.prototype.expose = function () {
+ // #ifndef APP-PLUS-NVUE
+ for (let i = this.stack.length; i--;) {
+ const item = this.stack[i]
+ if (item.c || item.name === 'a' || item.name === 'video' || item.name === 'audio') return
+ item.c = 1
+ }
+ // #endif
+}
+
+/**
+ * @description 处理插件
+ * @param {Object} node 要处理的标签
+ * @returns {Boolean} 是否要移除此标签
+ */
+Parser.prototype.hook = function (node) {
+ for (let i = this.plugins.length; i--;) {
+ if (this.plugins[i].onParse && this.plugins[i].onParse(node, this) === false) {
+ return false
+ }
+ }
+ return true
+}
+
+/**
+ * @description 将链接拼接上主域名
+ * @param {String} url 需要拼接的链接
+ * @returns {String} 拼接后的链接
+ */
+Parser.prototype.getUrl = function (url) {
+ const domain = this.options.domain
+ if (url[0] === '/') {
+ if (url[1] === '/') {
+ // // 开头的补充协议名
+ url = (domain ? domain.split('://')[0] : 'http') + ':' + url
+ } else if (domain) {
+ // 否则补充整个域名
+ url = domain + url
+ }
+ } else if (domain && !url.includes('data:') && !url.includes('://')) {
+ url = domain + '/' + url
+ }
+ return url
+}
+
+/**
+ * @description 解析样式表
+ * @param {Object} node 标签
+ * @returns {Object}
+ */
+Parser.prototype.parseStyle = function (node) {
+ const attrs = node.attrs
+ const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';'))
+ const styleObj = {}
+ let tmp = ''
+
+ if (attrs.id && !this.xml) {
+ // 暴露锚点
+ if (this.options.useAnchor) {
+ this.expose()
+ } else if (node.name !== 'img' && node.name !== 'a' && node.name !== 'video' && node.name !== 'audio') {
+ attrs.id = undefined
+ }
+ }
+
+ // 转换 width 和 height 属性
+ if (attrs.width) {
+ styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')
+ attrs.width = undefined
+ }
+ if (attrs.height) {
+ styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')
+ attrs.height = undefined
+ }
+
+ for (let i = 0, len = list.length; i < len; i++) {
+ const info = list[i].split(':')
+ if (info.length < 2) continue
+ const key = info.shift().trim().toLowerCase()
+ let value = info.join(':').trim()
+ if ((value[0] === '-' && value.lastIndexOf('-') > 0) || value.includes('safe')) {
+ // 兼容性的 css 不压缩
+ tmp += `;${key}:${value}`
+ } else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) {
+ // 重复的样式进行覆盖
+ if (value.includes('url')) {
+ // 填充链接
+ let j = value.indexOf('(') + 1
+ if (j) {
+ while (value[j] === '"' || value[j] === "'" || blankChar[value[j]]) {
+ j++
+ }
+ value = value.substr(0, j) + this.getUrl(value.substr(j))
+ }
+ } else if (value.includes('rpx')) {
+ // 转换 rpx(rich-text 内部不支持 rpx)
+ value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px')
+ }
+ styleObj[key] = value
+ }
+ }
+
+ node.attrs.style = tmp
+ return styleObj
+}
+
+/**
+ * @description 解析到标签名
+ * @param {String} name 标签名
+ * @private
+ */
+Parser.prototype.onTagName = function (name) {
+ this.tagName = this.xml ? name : name.toLowerCase()
+ if (this.tagName === 'svg') {
+ this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感
+ }
+}
+
+/**
+ * @description 解析到属性名
+ * @param {String} name 属性名
+ * @private
+ */
+Parser.prototype.onAttrName = function (name) {
+ name = this.xml ? name : name.toLowerCase()
+ if (name.substr(0, 5) === 'data-') {
+ if (name === 'data-src' && !this.attrs.src) {
+ // data-src 自动转为 src
+ this.attrName = 'src'
+ } else if (this.tagName === 'img' || this.tagName === 'a') {
+ // a 和 img 标签保留 data- 的属性,可以在 imgtap 和 linktap 事件中使用
+ this.attrName = name
+ } else {
+ // 剩余的移除以减小大小
+ this.attrName = undefined
+ }
+ } else {
+ this.attrName = name
+ this.attrs[name] = 'T' // boolean 型属性缺省设置
+ }
+}
+
+/**
+ * @description 解析到属性值
+ * @param {String} val 属性值
+ * @private
+ */
+Parser.prototype.onAttrVal = function (val) {
+ const name = this.attrName || ''
+ if (name === 'style' || name === 'href') {
+ // 部分属性进行实体解码
+ this.attrs[name] = decodeEntity(val, true)
+ } else if (name.includes('src')) {
+ // 拼接主域名
+ this.attrs[name] = this.getUrl(decodeEntity(val, true))
+ } else if (name) {
+ this.attrs[name] = val
+ }
+}
+
+/**
+ * @description 解析到标签开始
+ * @param {Boolean} selfClose 是否有自闭合标识 />
+ * @private
+ */
+Parser.prototype.onOpenTag = function (selfClose) {
+ // 拼装 node
+ const node = Object.create(null)
+ node.name = this.tagName
+ node.attrs = this.attrs
+ // 避免因为自动 diff 使得 type 被设置为 null 导致部分内容不显示
+ if (this.options.nodes.length) {
+ node.type = 'node'
+ }
+ this.attrs = Object.create(null)
+
+ const attrs = node.attrs
+ const parent = this.stack[this.stack.length - 1]
+ const siblings = parent ? parent.children : this.nodes
+ const close = this.xml ? selfClose : config.voidTags[node.name]
+
+ // 替换标签名选择器
+ if (tagSelector[node.name]) {
+ attrs.class = tagSelector[node.name] + (attrs.class ? ' ' + attrs.class : '')
+ }
+
+ // 转换 embed 标签
+ if (node.name === 'embed') {
+ // #ifndef H5 || APP-PLUS
+ const src = attrs.src || ''
+ // 按照后缀名和 type 将 embed 转为 video 或 audio
+ if (src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8') || (attrs.type || '').includes('video')) {
+ node.name = 'video'
+ } else if (src.includes('.mp3') || src.includes('.wav') || src.includes('.aac') || src.includes('.m4a') || (attrs.type || '').includes('audio')) {
+ node.name = 'audio'
+ }
+ if (attrs.autostart) {
+ attrs.autoplay = 'T'
+ }
+ attrs.controls = 'T'
+ // #endif
+ // #ifdef H5 || APP-PLUS
+ this.expose()
+ // #endif
+ }
+
+ // #ifndef APP-PLUS-NVUE
+ // 处理音视频
+ if (node.name === 'video' || node.name === 'audio') {
+ // 设置 id 以便获取 context
+ if (node.name === 'video' && !attrs.id) {
+ attrs.id = 'v' + idIndex++
+ }
+ // 没有设置 controls 也没有设置 autoplay 的自动设置 controls
+ if (!attrs.controls && !attrs.autoplay) {
+ attrs.controls = 'T'
+ }
+ // 用数组存储所有可用的 source
+ node.src = []
+ if (attrs.src) {
+ node.src.push(attrs.src)
+ attrs.src = undefined
+ }
+ this.expose()
+ }
+ // #endif
+
+ // 处理自闭合标签
+ if (close) {
+ if (!this.hook(node) || config.ignoreTags[node.name]) {
+ // 通过 base 标签设置主域名
+ if (node.name === 'base' && !this.options.domain) {
+ this.options.domain = attrs.href
+ } /* #ifndef APP-PLUS-NVUE */ else if (node.name === 'source' && parent && (parent.name === 'video' || parent.name === 'audio') && attrs.src) {
+ // 设置 source 标签(仅父节点为 video 或 audio 时有效)
+ parent.src.push(attrs.src)
+ } /* #endif */
+ return
+ }
+
+ // 解析 style
+ const styleObj = this.parseStyle(node)
+
+ // 处理图片
+ if (node.name === 'img') {
+ if (attrs.src) {
+ // 标记 webp
+ if (attrs.src.includes('webp')) {
+ node.webp = 'T'
+ }
+ // data url 图片如果没有设置 original-src 默认为不可预览的小图片
+ if (attrs.src.includes('data:') && !attrs['original-src']) {
+ attrs.ignore = 'T'
+ }
+ if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {
+ for (let i = this.stack.length; i--;) {
+ const item = this.stack[i]
+ if (item.name === 'a') {
+ node.a = item.attrs
+ break
+ }
+ // #ifndef H5 || APP-PLUS
+ const style = item.attrs.style || ''
+ if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || !styleObj.width.includes('%'))) {
+ styleObj.width = '100% !important'
+ styleObj.height = ''
+ for (let j = i + 1; j < this.stack.length; j++) {
+ this.stack[j].attrs.style = (this.stack[j].attrs.style || '').replace('inline-', '')
+ }
+ } else if (style.includes('flex') && styleObj.width === '100%') {
+ for (let j = i + 1; j < this.stack.length; j++) {
+ const style = this.stack[j].attrs.style || ''
+ if (!style.includes(';width') && !style.includes(' width') && style.indexOf('width') !== 0) {
+ styleObj.width = ''
+ break
+ }
+ }
+ } else if (style.includes('inline-block')) {
+ if (styleObj.width && styleObj.width[styleObj.width.length - 1] === '%') {
+ item.attrs.style += ';max-width:' + styleObj.width
+ styleObj.width = ''
+ } else {
+ item.attrs.style += ';max-width:100%'
+ }
+ }
+ // #endif
+ item.c = 1
+ }
+ attrs.i = this.imgList.length.toString()
+ let src = attrs['original-src'] || attrs.src
+ // #ifndef H5 || MP-ALIPAY || APP-PLUS || MP-360
+ if (this.imgList.includes(src)) {
+ // 如果有重复的链接则对域名进行随机大小写变换避免预览时错位
+ let i = src.indexOf('://')
+ if (i !== -1) {
+ i += 3
+ let newSrc = src.substr(0, i)
+ for (; i < src.length; i++) {
+ if (src[i] === '/') break
+ newSrc += Math.random() > 0.5 ? src[i].toUpperCase() : src[i]
+ }
+ newSrc += src.substr(i)
+ src = newSrc
+ }
+ }
+ // #endif
+ this.imgList.push(src)
+ // #ifdef H5 || APP-PLUS
+ if (this.options.lazyLoad) {
+ attrs['data-src'] = attrs.src
+ attrs.src = undefined
+ }
+ // #endif
+ }
+ }
+ if (styleObj.display === 'inline') {
+ styleObj.display = ''
+ }
+ // #ifndef APP-PLUS-NVUE
+ if (attrs.ignore) {
+ styleObj['max-width'] = styleObj['max-width'] || '100%'
+ attrs.style += ';-webkit-touch-callout:none'
+ }
+ // #endif
+ // 设置的宽度超出屏幕,为避免变形,高度转为自动
+ if (parseInt(styleObj.width) > windowWidth) {
+ styleObj.height = undefined
+ }
+ // 记录是否设置了宽高
+ if (styleObj.width) {
+ if (styleObj.width.includes('auto')) {
+ styleObj.width = ''
+ } else {
+ node.w = 'T'
+ if (!isNaN(parseInt(styleObj.height)) && (!styleObj.height.includes('%') || (parent && (parent.attrs.style || '').includes('height')))) {
+ node.h = 'T'
+ }
+ }
+ }
+ } else if (node.name === 'svg') {
+ siblings.push(node)
+ this.stack.push(node)
+ this.popNode()
+ return
+ }
+ for (const key in styleObj) {
+ if (styleObj[key]) {
+ attrs.style += `;${key}:${styleObj[key].replace(' !important', '')}`
+ }
+ }
+ attrs.style = attrs.style.substr(1) || undefined
+ } else {
+ if ((node.name === 'pre' || ((attrs.style || '').includes('white-space') && attrs.style.includes('pre'))) && this.pre !== 2) {
+ this.pre = node.pre = 1
+ }
+ node.children = []
+ this.stack.push(node)
+ }
+
+ // 加入节点树
+ siblings.push(node)
+}
+
+/**
+ * @description 解析到标签结束
+ * @param {String} name 标签名
+ * @private
+ */
+Parser.prototype.onCloseTag = function (name) {
+ // 依次出栈到匹配为止
+ name = this.xml ? name : name.toLowerCase()
+ let i
+ for (i = this.stack.length; i--;) {
+ if (this.stack[i].name === name) break
+ }
+ if (i !== -1) {
+ while (this.stack.length > i) {
+ this.popNode()
+ }
+ } else if (name === 'p' || name === 'br') {
+ const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
+ siblings.push({
+ name,
+ attrs: {
+ class: tagSelector[name],
+ style: this.tagStyle[name]
+ }
+ })
+ }
+}
+
+/**
+ * @description 处理标签出栈
+ * @private
+ */
+Parser.prototype.popNode = function () {
+ const node = this.stack.pop()
+ let attrs = node.attrs
+ const children = node.children
+ const parent = this.stack[this.stack.length - 1]
+ const siblings = parent ? parent.children : this.nodes
+
+ if (!this.hook(node) || config.ignoreTags[node.name]) {
+ // 获取标题
+ if (node.name === 'title' && children.length && children[0].type === 'text' && this.options.setTitle) {
+ uni.setNavigationBarTitle({
+ title: children[0].text
+ })
+ }
+ siblings.pop()
+ return
+ }
+
+ if (node.pre && this.pre !== 2) {
+ // 是否合并空白符标识
+ this.pre = node.pre = undefined
+ for (let i = this.stack.length; i--;) {
+ if (this.stack[i].pre) {
+ this.pre = 1
+ }
+ }
+ }
+
+ const styleObj = {}
+
+ // 转换 svg
+ if (node.name === 'svg') {
+ if (this.xml > 1) {
+ // 多层 svg 嵌套
+ this.xml--
+ return
+ }
+ // #ifdef APP-PLUS-NVUE
+ (function traversal (node) {
+ if (node.name) {
+ // 调整 svg 的大小写
+ node.name = config.svgDict[node.name] || node.name
+ for (const item in node.attrs) {
+ if (config.svgDict[item]) {
+ node.attrs[config.svgDict[item]] = node.attrs[item]
+ node.attrs[item] = undefined
+ }
+ }
+ for (let i = 0; i < (node.children || []).length; i++) {
+ traversal(node.children[i])
+ }
+ }
+ })(node)
+ // #endif
+ // #ifndef APP-PLUS-NVUE
+ let src = ''
+ const style = attrs.style
+ attrs.style = ''
+ attrs.xmlns = 'http://www.w3.org/2000/svg';
+ (function traversal (node) {
+ if (node.type === 'text') {
+ src += node.text
+ return
+ }
+ const name = config.svgDict[node.name] || node.name
+ src += '<' + name
+ for (const item in node.attrs) {
+ const val = node.attrs[item]
+ if (val) {
+ src += ` ${config.svgDict[item] || item}="${val}"`
+ }
+ }
+ if (!node.children) {
+ src += '/>'
+ } else {
+ src += '>'
+ for (let i = 0; i < node.children.length; i++) {
+ traversal(node.children[i])
+ }
+ src += '' + name + '>'
+ }
+ })(node)
+ node.name = 'img'
+ node.attrs = {
+ src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
+ style,
+ ignore: 'T'
+ }
+ node.children = undefined
+ // #endif
+ this.xml = false
+ return
+ }
+
+ // #ifndef APP-PLUS-NVUE
+ // 转换 align 属性
+ if (attrs.align) {
+ if (node.name === 'table') {
+ if (attrs.align === 'center') {
+ styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'
+ } else {
+ styleObj.float = attrs.align
+ }
+ } else {
+ styleObj['text-align'] = attrs.align
+ }
+ attrs.align = undefined
+ }
+
+ // 转换 dir 属性
+ if (attrs.dir) {
+ styleObj.direction = attrs.dir
+ attrs.dir = undefined
+ }
+
+ // 转换 font 标签的属性
+ if (node.name === 'font') {
+ if (attrs.color) {
+ styleObj.color = attrs.color
+ attrs.color = undefined
+ }
+ if (attrs.face) {
+ styleObj['font-family'] = attrs.face
+ attrs.face = undefined
+ }
+ if (attrs.size) {
+ let size = parseInt(attrs.size)
+ if (!isNaN(size)) {
+ if (size < 1) {
+ size = 1
+ } else if (size > 7) {
+ size = 7
+ }
+ styleObj['font-size'] = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'xxx-large'][size - 1]
+ }
+ attrs.size = undefined
+ }
+ }
+ // #endif
+
+ // 一些编辑器的自带 class
+ if ((attrs.class || '').includes('align-center')) {
+ styleObj['text-align'] = 'center'
+ }
+
+ Object.assign(styleObj, this.parseStyle(node))
+
+ if (node.name !== 'table' && parseInt(styleObj.width) > windowWidth) {
+ styleObj['max-width'] = '100%'
+ styleObj['box-sizing'] = 'border-box'
+ }
+
+ // #ifndef APP-PLUS-NVUE
+ if (config.blockTags[node.name]) {
+ node.name = 'div'
+ } else if (!config.trustTags[node.name] && !this.xml) {
+ // 未知标签转为 span,避免无法显示
+ node.name = 'span'
+ }
+
+ if (node.name === 'a' || node.name === 'ad'
+ // #ifdef H5 || APP-PLUS
+ || node.name === 'iframe' // eslint-disable-line
+ // #endif
+ ) {
+ this.expose()
+ } /* #ifdef APP-PLUS */ else if (node.name === 'video') {
+ let str = ''
+ node.html = str
+ } /* #endif */ else if ((node.name === 'ul' || node.name === 'ol') && node.c) {
+ // 列表处理
+ const types = {
+ a: 'lower-alpha',
+ A: 'upper-alpha',
+ i: 'lower-roman',
+ I: 'upper-roman'
+ }
+ if (types[attrs.type]) {
+ attrs.style += ';list-style-type:' + types[attrs.type]
+ attrs.type = undefined
+ }
+ for (let i = children.length; i--;) {
+ if (children[i].name === 'li') {
+ children[i].c = 1
+ }
+ }
+ } else if (node.name === 'table') {
+ // 表格处理
+ // cellpadding、cellspacing、border 这几个常用表格属性需要通过转换实现
+ let padding = parseFloat(attrs.cellpadding)
+ let spacing = parseFloat(attrs.cellspacing)
+ const border = parseFloat(attrs.border)
+ if (node.c) {
+ // padding 和 spacing 默认 2
+ if (isNaN(padding)) {
+ padding = 2
+ }
+ if (isNaN(spacing)) {
+ spacing = 2
+ }
+ }
+ if (border) {
+ attrs.style += ';border:' + border + 'px solid gray'
+ }
+ if (node.flag && node.c) {
+ // 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现
+ styleObj.display = 'grid'
+ if (spacing) {
+ styleObj['grid-gap'] = spacing + 'px'
+ styleObj.padding = spacing + 'px'
+ } else if (border) {
+ // 无间隔的情况下避免边框重叠
+ attrs.style += ';border-left:0;border-top:0'
+ }
+
+ const width = [] // 表格的列宽
+ const trList = [] // tr 列表
+ const cells = [] // 保存新的单元格
+ const map = {}; // 被合并单元格占用的格子
+
+ (function traversal (nodes) {
+ for (let i = 0; i < nodes.length; i++) {
+ if (nodes[i].name === 'tr') {
+ trList.push(nodes[i])
+ } else {
+ traversal(nodes[i].children || [])
+ }
+ }
+ })(children)
+
+ for (let row = 1; row <= trList.length; row++) {
+ let col = 1
+ for (let j = 0; j < trList[row - 1].children.length; j++, col++) {
+ const td = trList[row - 1].children[j]
+ if (td.name === 'td' || td.name === 'th') {
+ // 这个格子被上面的单元格占用,则列号++
+ while (map[row + '.' + col]) {
+ col++
+ }
+ let style = td.attrs.style || ''
+ const start = style.indexOf('width') ? style.indexOf(';width') : 0
+ // 提取出 td 的宽度
+ if (start !== -1) {
+ let end = style.indexOf(';', start + 6)
+ if (end === -1) {
+ end = style.length
+ }
+ if (!td.attrs.colspan) {
+ width[col] = style.substring(start ? start + 7 : 6, end)
+ }
+ style = style.substr(0, start) + style.substr(end)
+ }
+ style += (border ? `;border:${border}px solid gray` + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '')
+ // 处理列合并
+ if (td.attrs.colspan) {
+ style += `;grid-column-start:${col};grid-column-end:${col + parseInt(td.attrs.colspan)}`
+ if (!td.attrs.rowspan) {
+ style += `;grid-row-start:${row};grid-row-end:${row + 1}`
+ }
+ col += parseInt(td.attrs.colspan) - 1
+ }
+ // 处理行合并
+ if (td.attrs.rowspan) {
+ style += `;grid-row-start:${row};grid-row-end:${row + parseInt(td.attrs.rowspan)}`
+ if (!td.attrs.colspan) {
+ style += `;grid-column-start:${col};grid-column-end:${col + 1}`
+ }
+ // 记录下方单元格被占用
+ for (let rowspan = 1; rowspan < td.attrs.rowspan; rowspan++) {
+ for (let colspan = 0; colspan < (td.attrs.colspan || 1); colspan++) {
+ map[(row + rowspan) + '.' + (col - colspan)] = 1
+ }
+ }
+ }
+ if (style) {
+ td.attrs.style = style
+ }
+ cells.push(td)
+ }
+ }
+ if (row === 1) {
+ let temp = ''
+ for (let i = 1; i < col; i++) {
+ temp += (width[i] ? width[i] : 'auto') + ' '
+ }
+ styleObj['grid-template-columns'] = temp
+ }
+ }
+ node.children = cells
+ } else {
+ // 没有使用合并单元格的表格通过 table 布局实现
+ if (node.c) {
+ styleObj.display = 'table'
+ }
+ if (!isNaN(spacing)) {
+ styleObj['border-spacing'] = spacing + 'px'
+ }
+ if (border || padding) {
+ // 遍历
+ (function traversal (nodes) {
+ for (let i = 0; i < nodes.length; i++) {
+ const td = nodes[i]
+ if (td.name === 'th' || td.name === 'td') {
+ if (border) {
+ td.attrs.style = `border:${border}px solid gray;${td.attrs.style || ''}`
+ }
+ if (padding) {
+ td.attrs.style = `padding:${padding}px;${td.attrs.style || ''}`
+ }
+ } else if (td.children) {
+ traversal(td.children)
+ }
+ }
+ })(children)
+ }
+ }
+ // 给表格添加一个单独的横向滚动层
+ if (this.options.scrollTable && !(attrs.style || '').includes('inline')) {
+ const table = Object.assign({}, node)
+ node.name = 'div'
+ node.attrs = {
+ style: 'overflow:auto'
+ }
+ node.children = [table]
+ attrs = table.attrs
+ }
+ } else if ((node.name === 'td' || node.name === 'th') && (attrs.colspan || attrs.rowspan)) {
+ for (let i = this.stack.length; i--;) {
+ if (this.stack[i].name === 'table') {
+ this.stack[i].flag = 1 // 指示含有合并单元格
+ break
+ }
+ }
+ } else if (node.name === 'ruby') {
+ // 转换 ruby
+ node.name = 'span'
+ for (let i = 0; i < children.length - 1; i++) {
+ if (children[i].type === 'text' && children[i + 1].name === 'rt') {
+ children[i] = {
+ name: 'div',
+ attrs: {
+ style: 'display:inline-block;text-align:center'
+ },
+ children: [{
+ name: 'div',
+ attrs: {
+ style: 'font-size:50%;' + (children[i + 1].attrs.style || '')
+ },
+ children: children[i + 1].children
+ }, children[i]]
+ }
+ children.splice(i + 1, 1)
+ }
+ }
+ } else if (node.c) {
+ node.c = 2
+ for (let i = node.children.length; i--;) {
+ const child = node.children[i]
+ // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
+ if (child.name && (config.inlineTags[child.name] || (child.attrs.style || '').includes('inline'))) {
+ child.c = 1
+ }
+ // #endif
+ if (!child.c || child.name === 'table') {
+ node.c = 1
+ }
+ }
+ }
+
+ if ((styleObj.display || '').includes('flex') && !node.c) {
+ for (let i = children.length; i--;) {
+ const item = children[i]
+ if (item.f) {
+ item.attrs.style = (item.attrs.style || '') + item.f
+ item.f = undefined
+ }
+ }
+ }
+ // flex 布局时部分样式需要提取到 rich-text 外层
+ const flex = parent && (parent.attrs.style || '').includes('flex')
+ // #ifdef MP-WEIXIN
+ // 检查基础库版本 virtualHost 是否可用
+ && !(node.c && wx.getNFCAdapter) // eslint-disable-line
+ // #endif
+ // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
+ && !node.c // eslint-disable-line
+ // #endif
+ if (flex) {
+ node.f = ';max-width:100%'
+ }
+
+ // 优化长内容加载速度
+ if (children.length >= 50 && node.c && !(styleObj.display || '').includes('flex')) {
+ let i = children.length - 1
+ for (let j = i; j >= -1; j--) {
+ // 合并多个块级标签
+ if (j === -1 || children[j].c || !children[j].name || (children[j].name !== 'div' && children[j].name !== 'p' && children[j].name[0] !== 'h') || (children[j].attrs.style || '').includes('inline')) {
+ if (i - j >= 5) {
+ children.splice(j + 1, i - j, {
+ name: 'div',
+ attrs: {},
+ children: node.children.slice(j + 1, i + 1)
+ })
+ }
+ i = j - 1
+ }
+ }
+ }
+ // #endif
+
+ for (const key in styleObj) {
+ if (styleObj[key]) {
+ const val = `;${key}:${styleObj[key].replace(' !important', '')}`
+ /* #ifndef APP-PLUS-NVUE */
+ if (flex && ((key.includes('flex') && key !== 'flex-direction') || key === 'align-self' || styleObj[key][0] === '-' || (key === 'width' && val.includes('%')))) {
+ node.f += val
+ if (key === 'width') {
+ attrs.style += ';width:100%'
+ }
+ } else /* #endif */ {
+ attrs.style += val
+ }
+ }
+ }
+ attrs.style = attrs.style.substr(1) || undefined
+ // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
+ if (!attrs.style) {
+ delete attrs.style
+ }
+ // #endif
+}
+
+/**
+ * @description 解析到文本
+ * @param {String} text 文本内容
+ */
+Parser.prototype.onText = function (text) {
+ if (!this.pre) {
+ // 合并空白符
+ let trim = ''
+ let flag
+ for (let i = 0, len = text.length; i < len; i++) {
+ if (!blankChar[text[i]]) {
+ trim += text[i]
+ } else {
+ if (trim[trim.length - 1] !== ' ') {
+ trim += ' '
+ }
+ if (text[i] === '\n' && !flag) {
+ flag = true
+ }
+ }
+ }
+ // 去除含有换行符的空串
+ if (trim === ' ' && flag) return
+ text = trim
+ }
+ const node = Object.create(null)
+ node.type = 'text'
+ // #ifdef (MP-BAIDU || MP-ALIPAY || MP-TOUTIAO) && VUE3
+ node.attrs = {}
+ // #endif
+ node.text = decodeEntity(text)
+ if (this.hook(node)) {
+ // #ifdef MP-WEIXIN
+ if (this.options.selectable === 'force' && system.includes('iOS')) {
+ this.expose()
+ node.us = 'T'
+ }
+ // #endif
+ const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
+ siblings.push(node)
+ }
+}
+
+/**
+ * @description html 词法分析器
+ * @param {Object} handler 高层处理器
+ */
+function Lexer (handler) {
+ this.handler = handler
+}
+
+/**
+ * @description 执行解析
+ * @param {String} content 要解析的文本
+ */
+Lexer.prototype.parse = function (content) {
+ this.content = content || ''
+ this.i = 0 // 标记解析位置
+ this.start = 0 // 标记一个单词的开始位置
+ this.state = this.text // 当前状态
+ for (let len = this.content.length; this.i !== -1 && this.i < len;) {
+ this.state()
+ }
+}
+
+/**
+ * @description 检查标签是否闭合
+ * @param {String} method 如果闭合要进行的操作
+ * @returns {Boolean} 是否闭合
+ * @private
+ */
+Lexer.prototype.checkClose = function (method) {
+ const selfClose = this.content[this.i] === '/'
+ if (this.content[this.i] === '>' || (selfClose && this.content[this.i + 1] === '>')) {
+ if (method) {
+ this.handler[method](this.content.substring(this.start, this.i))
+ }
+ this.i += selfClose ? 2 : 1
+ this.start = this.i
+ this.handler.onOpenTag(selfClose)
+ if (this.handler.tagName === 'script') {
+ this.i = this.content.indexOf('', this.i)
+ if (this.i !== -1) {
+ this.i += 2
+ this.start = this.i
+ }
+ this.state = this.endTag
+ } else {
+ this.state = this.text
+ }
+ return true
+ }
+ return false
+}
+
+/**
+ * @description 文本状态
+ * @private
+ */
+Lexer.prototype.text = function () {
+ this.i = this.content.indexOf('<', this.i) // 查找最近的标签
+ if (this.i === -1) {
+ // 没有标签了
+ if (this.start < this.content.length) {
+ this.handler.onText(this.content.substring(this.start, this.content.length))
+ }
+ return
+ }
+ const c = this.content[this.i + 1]
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+ // 标签开头
+ if (this.start !== this.i) {
+ this.handler.onText(this.content.substring(this.start, this.i))
+ }
+ this.start = ++this.i
+ this.state = this.tagName
+ } else if (c === '/' || c === '!' || c === '?') {
+ if (this.start !== this.i) {
+ this.handler.onText(this.content.substring(this.start, this.i))
+ }
+ const next = this.content[this.i + 2]
+ if (c === '/' && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
+ // 标签结尾
+ this.i += 2
+ this.start = this.i
+ this.state = this.endTag
+ return
+ }
+ // 处理注释
+ let end = '-->'
+ if (c !== '!' || this.content[this.i + 2] !== '-' || this.content[this.i + 3] !== '-') {
+ end = '>'
+ }
+ this.i = this.content.indexOf(end, this.i)
+ if (this.i !== -1) {
+ this.i += end.length
+ this.start = this.i
+ }
+ } else {
+ this.i++
+ }
+}
+
+/**
+ * @description 标签名状态
+ * @private
+ */
+Lexer.prototype.tagName = function () {
+ if (blankChar[this.content[this.i]]) {
+ // 解析到标签名
+ this.handler.onTagName(this.content.substring(this.start, this.i))
+ while (blankChar[this.content[++this.i]]);
+ if (this.i < this.content.length && !this.checkClose()) {
+ this.start = this.i
+ this.state = this.attrName
+ }
+ } else if (!this.checkClose('onTagName')) {
+ this.i++
+ }
+}
+
+/**
+ * @description 属性名状态
+ * @private
+ */
+Lexer.prototype.attrName = function () {
+ let c = this.content[this.i]
+ if (blankChar[c] || c === '=') {
+ // 解析到属性名
+ this.handler.onAttrName(this.content.substring(this.start, this.i))
+ let needVal = c === '='
+ const len = this.content.length
+ while (++this.i < len) {
+ c = this.content[this.i]
+ if (!blankChar[c]) {
+ if (this.checkClose()) return
+ if (needVal) {
+ // 等号后遇到第一个非空字符
+ this.start = this.i
+ this.state = this.attrVal
+ return
+ }
+ if (this.content[this.i] === '=') {
+ needVal = true
+ } else {
+ this.start = this.i
+ this.state = this.attrName
+ return
+ }
+ }
+ }
+ } else if (!this.checkClose('onAttrName')) {
+ this.i++
+ }
+}
+
+/**
+ * @description 属性值状态
+ * @private
+ */
+Lexer.prototype.attrVal = function () {
+ const c = this.content[this.i]
+ const len = this.content.length
+ if (c === '"' || c === "'") {
+ // 有冒号的属性
+ this.start = ++this.i
+ this.i = this.content.indexOf(c, this.i)
+ if (this.i === -1) return
+ this.handler.onAttrVal(this.content.substring(this.start, this.i))
+ } else {
+ // 没有冒号的属性
+ for (; this.i < len; this.i++) {
+ if (blankChar[this.content[this.i]]) {
+ this.handler.onAttrVal(this.content.substring(this.start, this.i))
+ break
+ } else if (this.checkClose('onAttrVal')) return
+ }
+ }
+ while (blankChar[this.content[++this.i]]);
+ if (this.i < len && !this.checkClose()) {
+ this.start = this.i
+ this.state = this.attrName
+ }
+}
+
+/**
+ * @description 结束标签状态
+ * @returns {String} 结束的标签名
+ * @private
+ */
+Lexer.prototype.endTag = function () {
+ const c = this.content[this.i]
+ if (blankChar[c] || c === '>' || c === '/') {
+ this.handler.onCloseTag(this.content.substring(this.start, this.i))
+ if (c !== '>') {
+ this.i = this.content.indexOf('>', this.i)
+ if (this.i === -1) return
+ }
+ this.start = ++this.i
+ this.state = this.text
+ } else {
+ this.i++
+ }
+}
+
+export default Parser
diff --git a/sheep/ui/su-parse/su-parse.vue b/sheep/ui/su-parse/su-parse.vue
new file mode 100644
index 00000000..3a587f2e
--- /dev/null
+++ b/sheep/ui/su-parse/su-parse.vue
@@ -0,0 +1,430 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-popover/su-popover.vue b/sheep/ui/su-popover/su-popover.vue
new file mode 100644
index 00000000..adff127c
--- /dev/null
+++ b/sheep/ui/su-popover/su-popover.vue
@@ -0,0 +1,314 @@
+
+
+
+
+
+
+
+
+ {{ tips }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-popup/keypress.js b/sheep/ui/su-popup/keypress.js
new file mode 100644
index 00000000..6141c4c8
--- /dev/null
+++ b/sheep/ui/su-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/sheep/ui/su-popup/su-popup.vue b/sheep/ui/su-popup/su-popup.vue
new file mode 100644
index 00000000..bf580f46
--- /dev/null
+++ b/sheep/ui/su-popup/su-popup.vue
@@ -0,0 +1,589 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-progress/su-progress.vue b/sheep/ui/su-progress/su-progress.vue
new file mode 100644
index 00000000..b6aff5af
--- /dev/null
+++ b/sheep/ui/su-progress/su-progress.vue
@@ -0,0 +1,200 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ percentage }}%
+
+
+
+
+ {{ percentage }}%
+
+
+
+
+ {{ percentage }}%
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-radio/su-radio.vue b/sheep/ui/su-radio/su-radio.vue
new file mode 100644
index 00000000..6fc4f74e
--- /dev/null
+++ b/sheep/ui/su-radio/su-radio.vue
@@ -0,0 +1,301 @@
+
+
+
+
+
+
+
+ {{ label }}
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-region-picker/su-region-picker.vue b/sheep/ui/su-region-picker/su-region-picker.vue
new file mode 100644
index 00000000..ab3d3cdc
--- /dev/null
+++ b/sheep/ui/su-region-picker/su-region-picker.vue
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+
+
+ {{ province.name }}
+
+
+
+
+ {{ city.name }}
+
+
+
+
+ {{ district.name }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-status-bar/su-status-bar.vue b/sheep/ui/su-status-bar/su-status-bar.vue
new file mode 100644
index 00000000..40709d2a
--- /dev/null
+++ b/sheep/ui/su-status-bar/su-status-bar.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/sheep/ui/su-sticky/su-sticky.vue b/sheep/ui/su-sticky/su-sticky.vue
new file mode 100644
index 00000000..a8831a26
--- /dev/null
+++ b/sheep/ui/su-sticky/su-sticky.vue
@@ -0,0 +1,264 @@
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-subline/su-subline.vue b/sheep/ui/su-subline/su-subline.vue
new file mode 100644
index 00000000..fa09765f
--- /dev/null
+++ b/sheep/ui/su-subline/su-subline.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-swiper/su-swiper.vue b/sheep/ui/su-swiper/su-swiper.vue
new file mode 100644
index 00000000..5a44cd25
--- /dev/null
+++ b/sheep/ui/su-swiper/su-swiper.vue
@@ -0,0 +1,495 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ state.cur + 1 }} / {{ props.list.length }}
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-switch/su-switch.vue b/sheep/ui/su-switch/su-switch.vue
new file mode 100644
index 00000000..d63e8004
--- /dev/null
+++ b/sheep/ui/su-switch/su-switch.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-tab-item/su-tab-item.vue b/sheep/ui/su-tab-item/su-tab-item.vue
new file mode 100644
index 00000000..615df2f4
--- /dev/null
+++ b/sheep/ui/su-tab-item/su-tab-item.vue
@@ -0,0 +1,169 @@
+
+
+
+
+
+ {{ props.data.title }}
+
+
+ {{ props.data.tag }}
+
+
+ {{ props.data.subtitle }}
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-tab/su-tab.vue b/sheep/ui/su-tab/su-tab.vue
new file mode 100644
index 00000000..17a79836
--- /dev/null
+++ b/sheep/ui/su-tab/su-tab.vue
@@ -0,0 +1,474 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-tabbar-item/su-tabbar-item.vue b/sheep/ui/su-tabbar-item/su-tabbar-item.vue
new file mode 100644
index 00000000..e46a77b3
--- /dev/null
+++ b/sheep/ui/su-tabbar-item/su-tabbar-item.vue
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ text }}
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-tabbar/su-tabbar.vue b/sheep/ui/su-tabbar/su-tabbar.vue
new file mode 100644
index 00000000..3b6d2459
--- /dev/null
+++ b/sheep/ui/su-tabbar/su-tabbar.vue
@@ -0,0 +1,226 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-tabs-item/props.js b/sheep/ui/su-tabs-item/props.js
new file mode 100644
index 00000000..3908c369
--- /dev/null
+++ b/sheep/ui/su-tabs-item/props.js
@@ -0,0 +1,3 @@
+export default {
+ props: {},
+};
diff --git a/sheep/ui/su-tabs-item/su-tabs-item.vue b/sheep/ui/su-tabs-item/su-tabs-item.vue
new file mode 100644
index 00000000..71396847
--- /dev/null
+++ b/sheep/ui/su-tabs-item/su-tabs-item.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-tabs/su-tabs.vue b/sheep/ui/su-tabs/su-tabs.vue
new file mode 100644
index 00000000..05d7dcb3
--- /dev/null
+++ b/sheep/ui/su-tabs/su-tabs.vue
@@ -0,0 +1,434 @@
+
+
+
+
+
+
+
+
+ {{ item[keyName] }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-time-line/su-time-line.vue b/sheep/ui/su-time-line/su-time-line.vue
new file mode 100644
index 00000000..9c3f8c1b
--- /dev/null
+++ b/sheep/ui/su-time-line/su-time-line.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-timeline-item/su-timeline-item.vue b/sheep/ui/su-timeline-item/su-timeline-item.vue
new file mode 100644
index 00000000..ddf7a0ba
--- /dev/null
+++ b/sheep/ui/su-timeline-item/su-timeline-item.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-toolbar/su-toolbar.vue b/sheep/ui/su-toolbar/su-toolbar.vue
new file mode 100644
index 00000000..17351ece
--- /dev/null
+++ b/sheep/ui/su-toolbar/su-toolbar.vue
@@ -0,0 +1,129 @@
+
+
+
+
+ {{ cancelText }}
+
+
+ {{ title }}
+
+
+ {{ confirmText }}
+
+
+
+
+
+
+
+
diff --git a/sheep/ui/su-video/su-video.vue b/sheep/ui/su-video/su-video.vue
new file mode 100644
index 00000000..42ede7cf
--- /dev/null
+++ b/sheep/ui/su-video/su-video.vue
@@ -0,0 +1,212 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sheep/url/index.js b/sheep/url/index.js
new file mode 100644
index 00000000..61046722
--- /dev/null
+++ b/sheep/url/index.js
@@ -0,0 +1,199 @@
+import $store from '@/sheep/store';
+import { staticUrl } from '@/sheep/config';
+
+const cdn = (url = '', cdnurl = '') => {
+ if (!url) return '';
+ if (url.indexOf('http') === 0) {
+ return url;
+ }
+ if (cdnurl === '') {
+ cdnurl = $store('app').info.cdnurl;
+ }
+ return cdnurl + url;
+};
+export default {
+ // 添加cdn域名前缀
+ cdn,
+ // 对象存储自动剪裁缩略图
+ thumb: (url = '', params) => {
+ url = cdn(url);
+ return append_thumbnail_params(url, params);
+ },
+ // 静态资源地址
+ static: (url = '', staticurl = '') => {
+ if (staticurl === '') {
+ staticurl = staticUrl;
+ }
+ if (staticurl !== 'local') {
+ url = cdn(url, staticurl);
+ }
+ return url;
+ },
+ // css背景图片地址
+ css: (url = '', staticurl = '') => {
+ if (staticurl === '') {
+ staticurl = staticUrl;
+ }
+ if (staticurl !== 'local') {
+ url = cdn(url, staticurl);
+ }
+ // #ifdef APP-PLUS
+ if (staticurl === 'local') {
+ url = plus.io.convertLocalFileSystemURL(url);
+ }
+ // #endif
+ return `url(${url})`;
+ },
+};
+
+/**
+ * 追加对象存储自动裁剪/压缩参数
+ *
+ * @return string
+ */
+function append_thumbnail_params(url, params) {
+ const filesystem = $store('app').info.filesystem;
+ if (filesystem === 'public') {
+ return url;
+ }
+ let width = params.width || '200'; // 宽度
+ let height = params.height || '200'; // 高度
+ let mode = params.mode || 'lfit'; // 缩放模式
+ let quality = params.quality || 90; // 压缩质量
+ let gravity = params.gravity || 'center'; // 剪裁质量
+ let suffix = '';
+ let crop_str = '';
+ let quality_str = '';
+ let size = width + 'x' + height;
+ switch (filesystem) {
+ case 'aliyun':
+ // 裁剪
+ if (!gravity && gravity != 'center') {
+ // 指定了裁剪区域
+ mode = 'mfit';
+ crop_str = '/crop,g_' + gravityFormat('aliyun', gravity) + ',w_' + width + ',h_' + height;
+ }
+
+ // 质量压缩
+ if (quality > 0 && quality < 100) {
+ quality_str = '/quality,q_' + quality;
+ }
+
+ // 缩放参数
+ suffix = 'x-oss-process=image/resize,m_' + mode + ',w_' + width + ',h_' + height;
+
+ // 拼接裁剪和质量压缩
+ suffix += crop_str + quality_str;
+ break;
+ case 'qcloud':
+ let mode_str = 'thumbnail';
+ if (mode == 'fill' || (!gravity && gravity != 'center')) {
+ // 指定了裁剪区域
+ mode_str = 'crop';
+ mode = 'fill';
+ crop_str = '/gravity/' + gravityFormat('qcloud', gravity);
+ }
+
+ // 质量压缩
+ if (quality > 0 && quality < 100) {
+ quality_str = '/rquality/' + quality;
+ }
+
+ switch (mode) {
+ case 'lfit':
+ size = '' + size + '>';
+ break;
+ case 'mfit':
+ size = '!' + size + 'r';
+ case 'fill':
+ break;
+ case 'pad':
+ size = size + '/pad/1';
+ break;
+ case 'fixed':
+ size = size + '!';
+ break;
+ }
+
+ suffix = 'imageMogr2/' + mode_str + '/' + size + crop_str + quality_str;
+ break;
+ case 'qiniu':
+ if (mode == 'fill' || (!gravity && gravity != 'center')) {
+ // 指定了裁剪区域,全部转为 mfit
+ mode = 'mfit';
+ crop_str = '/gravity/' + gravityFormat('qiniu', gravity) + '/crop/' + size;
+ }
+ // 质量压缩
+ if (quality > 0 && quality < 100) {
+ quality_str = '/quality/' + quality;
+ }
+
+ switch (mode) {
+ case 'lfit':
+ case 'pad': // 七牛不支持在缩放之后,尺寸不足时,填充背景色,所以这里和 lfit 模式一样
+ size = size + '>';
+ break;
+ case 'mfit':
+ size = '!' + size + 'r';
+ break;
+ case 'fill':
+ // 会被转为 mfit
+ break;
+ case 'fixed':
+ size = size + '!';
+ break;
+ }
+
+ suffix = 'imageMogr2/thumbnail/' + size + crop_str + quality_str;
+ break;
+ }
+ return url + '?' + suffix;
+}
+
+/**
+ * 裁剪区域格式转换
+ *
+ * @param string $type aliyun|qcloud|qiniu
+ * @param string $gravity 统一的裁剪区域字符
+ *
+ * @return string
+ */
+function gravityFormatMap(type, gravity) {
+ let gravityFormat = {
+ aliyun: {
+ north_west: 'nw', // 左上
+ north: 'north', // 中上
+ north_east: 'ne', // 右上
+ west: 'west', // 左中
+ center: 'center', // 中部
+ east: 'east', // 右中
+ south_west: 'sw', // 左下
+ south: 'south', // 中下
+ south_east: 'se', // 右下
+ },
+ qcloud: {
+ northwest: 'nw', // 左上
+ north: 'north', // 中上
+ northeast: 'ne', // 右上
+ west: 'west', // 左中
+ center: 'center', // 中部
+ east: 'east', // 右中
+ southwest: 'sw', // 左下
+ south: 'south', // 中下
+ southeast: 'se', // 右下
+ },
+ qiniu: {
+ NorthWest: 'nw', // 左上
+ North: 'north', // 中上
+ NorthEast: 'ne', // 右上
+ West: 'west', // 左中
+ Center: 'center', // 中部
+ East: 'east', // 右中
+ SouthWest: 'sw', // 左下
+ South: 'south', // 中下
+ SouthEast: 'se', // 右下
+ },
+ };
+
+ return gravityFormat[type][gravity];
+}
diff --git a/sheep/validate/form.js b/sheep/validate/form.js
new file mode 100644
index 00000000..f3e338d6
--- /dev/null
+++ b/sheep/validate/form.js
@@ -0,0 +1,230 @@
+/**
+ * Validate v1.0.0 通用验证
+ * @description 项目中用到的表单验证规则
+ */
+import test from '@/sheep/helper/test.js';
+
+// 手机号
+export const mobile = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入手机号',
+ },
+ {
+ validateFunction: function (rule, value, data, callback) {
+ if (!test.mobile(value)) {
+ callback('手机号码格式不正确');
+ }
+ return true;
+ },
+ },
+ ],
+};
+
+// 账户
+export const account = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入账号',
+ },
+ {
+ validateFunction: function (rule, value, data, callback) {
+ if (value.length < 5) {
+ callback('账号长度不能小于5位');
+ }
+ return true;
+ },
+ },
+ ],
+};
+
+// 密码
+export const password = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入密码',
+ },
+ {
+ validateFunction: function (rule, value, data, callback) {
+ if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]+\S{5,12}$/.test(value)) {
+ callback('需包含字母和数字,长度在6-12之间');
+ }
+ return true;
+ },
+ },
+ ],
+};
+
+// 昵称
+export const username = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入用户名',
+ },
+ {
+ validateFunction: function (rule, value, data, callback) {
+ if (value.length < 5) {
+ callback('用户名长度不能小于5位');
+ }
+ return true;
+ },
+ },
+ ],
+};
+
+// 短信验证码
+export const code = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入验证码',
+ },
+ ],
+};
+
+// 真实姓名
+export const realName = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入姓名',
+ },
+ {
+ validateFunction: function (rule, value, data, callback) {
+ if (!test.chinese(value)) {
+ callback('请输入汉字');
+ }
+ return true;
+ },
+ },
+ ],
+};
+
+export const taxName = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入发票抬头名称',
+ },
+ {
+ validateFunction: function (rule, value, data, callback) {
+ if (!test.chinese(value)) {
+ callback('请输入汉字');
+ }
+ return true;
+ },
+ },
+ ],
+};
+
+// 税号
+export const taxNo = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入税号',
+ },
+ ],
+};
+
+// 开户行
+export const bankName = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入开户行',
+ },
+ {
+ validateFunction: function (rule, value, data, callback) {
+ if (!test.chinese(value)) {
+ callback('请输入汉字');
+ }
+ return true;
+ },
+ },
+ ],
+};
+// 银行卡号
+export const bankCode = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入银行卡号',
+ },
+ {
+ validateFunction: function (rule, value, data, callback) {
+ if (!test.number(value)) {
+ callback('请输入正确账号');
+ }
+ return true;
+ },
+ },
+ ],
+};
+
+// 支付宝账号
+export const alipayAccount = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入支付宝账号',
+ },
+ {
+ validateFunction: function (rule, value, data, callback) {
+ let isEmail = test.email(value);
+ let isMobile = test.mobile(value);
+
+ if (!isEmail && !isMobile) {
+ callback('请输入正确账号');
+ }
+ return true;
+ },
+ },
+ ],
+};
+
+export const consignee = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入收货人姓名',
+ },
+ ],
+};
+
+export const region = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请选择您的位置',
+ },
+ ],
+};
+export const address = {
+ rules: [
+ {
+ required: true,
+ errorMessage: '请输入详细地址',
+ },
+ ],
+};
+
+export default {
+ mobile,
+ alipayAccount,
+ bankCode,
+ bankName,
+ realName,
+ password,
+ code,
+ account,
+ consignee,
+ address,
+ region,
+ taxNo,
+ taxName,
+};
diff --git a/static/activity-left.png b/static/activity-left.png
new file mode 100644
index 0000000000000000000000000000000000000000..62e3b835ee5313f86e55b0a058df04c1729c280b
GIT binary patch
literal 440
zcmeAS@N?(olHy`uVBq!ia0vp^B|z-K!3-n?{wN*>QceLrA+A9B|MTnrpIwKN_s{=-
zegn#e2tL04|M8XoPjCExcH{rkt6&CD{f+-mZvB691*p-^&EqD}6w#6(zhDN1`}6M!
z#Mi&yUl8DMZhgb~efzopr0LnVe8UO$Q
literal 0
HcmV?d00001
diff --git a/static/activity-right.png b/static/activity-right.png
new file mode 100644
index 0000000000000000000000000000000000000000..e18a96c8314d3cb8e66478c8e06ed07688f2eb6a
GIT binary patch
literal 1400
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
KF{w53Nc)6RoWE+fOFf+`?
z%wX*N-v0gF&wcNG?s=bc&U4Ov|9U@|7-=(H<-AHpM#iA4qiF{0_`!WaD>7AbK3nE8JLH7{NXMT3LxzN
zBNKsFyTC@oV2A)`cb5RX!tLO3yMI`#kn5CA(CPXGeraCiWmoSt4>Tw<}ixE%rp
zw*#cWV0Y120PFzk(b3UAjKQHWJ17he4FD7lg~gqnodZYk;?g!62ULyUIo(8VgSrM_
z>zj*9%P0(X6S2Jp+k_)gO|3(SEfflkTZL_Iqp(Ec{@NN0XvijFtD#|NV-o>|uC;*%
z5lAEgi9&2`Bezi7D9q{_47anhyt0bMVBuS6AS`kl4J=#RsP#=a9J$rj(G7%cY90i2
z3@$B00b$!{Y-?Np^70B6i(7%N!I9f=#1^0cs0)q&FbdS!-O<&zxV(bKU|{PTq{E~3
zj&9^O766DX^y1}P%vxPzfE^we`6lQZ14qjXalET*4F&NAm3Ex
zi8Ot_cT9gidj@`X)m5AJY<{z#3qkUu35HWt(3!kp|4
z;ZqZpe$MF4w=YzNYFa8{SCzdr$jEMV>T0T4ge+s1x?Vy|*oO#gVb`)70vUp&-40gD
z+5fWBJ!4P*p~t77K9Wcqn*d|tqK$@e-eU4DDx@a4n}G&+*&kCd9}$Z&RuZdTN8Cb?n|Az8U6Xod0?;P`DL1hVY*X
zbX*=%;P@}%Thhsq!<>w?(K~}!Oa?~Q8zOu4fpY(0{aw~-(1>xsb=zlr=GDo%qswVx
zbSaM|hcqV>aX7*tfNJhbgp*vn&A4yQdK!Oy^41UP
zLwVZy^bbY4S!0whCt}k_U5n((2T9Cjet0G9eEO(?4M70aA;}pb75-!E52#HyvxqVt
z_tCA^498Vzx$?tc*eh%N?X@aTUqaC(7g>ukXHLJx!LEf4G)uyI@+|oBdY@Hl?syMep8xSuqFjcr
z^@40Z^)KNQ6cvU(uwN-6I~f$0BQL3~XH%VRw;46NtJ#=We~nFg(YBa^Ltnb4RX7hDxx;HFt{a}+$>5)%W%h+UUl{Pr)g7V^*q8hvZ=fMQ>hP$5HRV8_nc24cC
zXRiyo>VDIbH>`9hxt5QrvatTF`yI<2#|l>EZ1^VZMQ&=YQqS`tt(J>M86qq7MFn!q
zm61lblI6ir!$tXpv*~`tb|@j*_Uo;CN261Wq6i~dzgu41O0wGeZAIqJPZ492w$3lg
zL-Uog%Kv&r#>H2xblZ;!MUC42T}H&&I#0V*1ZXByOssAg@8ATQJmnZ+N4!cO*d%x?
zn2nP*#Apxr4LUsYQv8O+@*X(O;9M+KuXITg=u#eLRo97|t<2IZ9|N
z)?F5KvCn;T&E6KD8DBpCtr{}X^44Q%yJaBwGAin{k$E~qysFkY&odk6a6tVNr0q-L
zXZNsRGq^(1S|u9$s?+15#A=>B8ExeFD#G=xINFSLWx!SN4Ba+rwo|q2kY)61cP_5=
zY#vj#5k-=}zuFOe_C_w5UZ}<~Wa$EmvBgxiZ0@s1>OJcA=Q_0azu)Ds*6&Wp0bqqGZt+>O!mFX?Xmy%`J)J1OADZm$uOl_UCkN#AL
zfH;y!APt$u!?7VVtMg-*)0E%JbjrW!zws!F^!=&XfShX?pL=J9%ceuV7ut$HQW-ZB
z6J)Iq$x4`F*uNWVE@F78#$*111@Sp}Va38^vm^Xsbm`nW4esdtU{9l$a!8+^%9#J@
zod9_?PSdYcQhLmePm7j*EOfz1?V?PQLl5o?Jdek|IB^THuWGyLoDeGe#WJZrek*s{
zz;>2Uj=+=Kr5b#UNJV;PmrG}`aoo!l)KhYZcTbzNy~MBF;&kC0nB|HeWDO827&jDS
z^(aec0yuf8B&
zW}b9<8#DGi*XlK(=N8xt6)%wR_-q-ZSpOk8N7{Kj?Desq>)*YUUpaR}$y_4+w7#TJ
zwCIRQ=usHPZmZ|N4>CFl%
z`zESa%oprBHWg&+pFNX>)QUw|ZG^VZ6-}i*XdkN_+E?W
z$7
z^`Z5l;@nznx5#Rz;E|fz#?~`k%jQy;=>^aAvU6T)74`<}+KOI?2~Cu_Vh-Ec51k_G
zsdClB&eofUH^?712_F@T$5890S&T9((w>#dtxt)TmnR|oLksU^v-DM-aqtVX<;L)S
z)8n9_)FsxnQe287|M130yy2j^V<8g?v5n)E2&QQ+^NC=MD74^o!*h^(kRHsf?Fkt}
z`C=NTPKO$p*kfLmxvX?Gf9nb1tFwvdjf>$uyh1Hg5flNKMY4nuMM)&P4
zNgtCAlz21e`?812#eNHh*Js;V&d5+!Xg@|jEm>i3@R8>9{{>p`4{Q_Hn4N%4@;%N4
zDL2j**#2%;a@0}Ib8dBRkf*Z{u(T{(4s3Ei$lwd%7K#N`fhy)@XptO29Tqu4Jq8Y>
z=NTH(!%~rVqqO4BtOOqnbc5&*uFreX0cu|5R7lD&;1dYfS43S}5u#
zqqi@ZC(Q`Aoef^w)*4n>I`yYi&l{G%53hD|dQ_`3=?!RGV6OW*3A2t6PAX9kPrdQ8
z%@?nqTv(dGk6J?ia4;#i7(+h01KsAp2XVZK<*jihiL|yZEWj92V#9L-xa4u~oSl7`
z9Qf=M&XvFaaAwp&qhPC7ukye%{WZT&+>{F~)NmwdBfFE|^?bob~r9B3Q@gUYz
zLKXij87XTV*`K|=Nv~N6rYLtVp~AvcqSSl>HKJA6S}k0=s$7>+TPoQWedid`k|yMO
z(sv`;9vePcp&ia=L{SITc$?$*RLF#abHSgT=U^%XyA3=xa3P)76vKa-TigH4^Az3t0
z`^9X2!+G!JJ5MD-ZtnBTeOC|;+23i52~Vm0au~S~`FyzkEva*N_6n%w$|^i-U5J3}
zBt)o9*2cS}Jd!)RR9F~hJ9#($%&uSy)H65!&T8QX{P$IT-aV{Iu_pyQE+XeJdTrWy
zhu=HjMd{Zh8%VLWfhXZ4TwO`Q;v@A8=W4yQk5cMU<)B#B;Ymw}bSr_niz+(@wO(p$
zNyamx#XlIhJK57|&sr^Lc0WhkaV;p5oW3MkUN>I0g@jeD6wE-XICc5x*@z37Rg##4
zBSnrdZ1EGx+4j#T`y_>)8k%QuY^4i>Z%`)O(UG9m(0!=j7Bn%bW;JoG&O^Rn-{G#Q
zD2piV6JN|rN<|O|bkfYN6;PJ@3u@BzRP)BJk#=LVsx+8e&C&5{{Dokx8dm#;-fNO2
z#d`m?3DZIT68DuFk9o8tQf~Hs%3Ev0&+=B*jXfJlY;C81&itYWDtyz&Bxm&dc!jgM
zALjYbtau$+Y8Lu$v>rytmhlm<{5{N!DWsAQj58%__E`0?aGjhth5W8PN&I#qRG2`W
zNZzW?@g@iK%qqgbayM~qr*tPQD=Bcr&%N*4#OjNnI75%5gfW>g2&^W}vEO0=?{Kx0
zP*e9Ito}V+7Vi2XSIxaLE{5R9XOjA*?ipw8Q>MO_ha@sNV?7<;efo7nnt&QfGPcjK
z8}j6^NQdiIg)%8}e^5@VtSvLzNh6)-X>%Qo7DHMqp6?%VPc-)bXm8~dxnmx*uO~EL
z6svzlk|@{cB%48>(yeeh6FklOP^jcJL%kbxIGi3_DY3{a|d6g
z%EXCNuwtn3tnnkCJ@pSalU$a1p_8ES8BrovP&5`GG@>!k6J=-N(aup79G-i
z#1=1hkf%-FSWByJXvkn5?a0d^zLGk>oprd(*x0zuwjY-
zS(h))(^2b#a7z~Ux=#x})!CK|U9;KfAk^Ib*=>(^JX4&d_KY;cs^Sz^aCs%C=GQVF
z=bNU8k97L!p`q24c@kf_d8gbh^gqzltmVNe2U2rFBO&pQ)H@5syV1iXlwt0#PUcsk
z`*L*e$+@CzsLZ^sMgD|(HpbQYq)yj=NEyoN^D+p!F&R(E&J^}#zck!NY_=|=Is$nJvpfS_|#!s`!uq(Qd{Dl
zY=;{e^V|iW>@3P+_g*3BVfQ8cIJZ^eW^AZ;lXhNS@@c~j#86o+Eg5XS^`I@#@>v=U
zO)C$NvI3&uKnox7DeUk-mC4QWlda?4yN4ECxn!1K1a$LWuSM?fAMM*(ocohr_%%E<
zDKS9}8Cx+VQq7m=u*lPwm^?k%9M&G@RqjzaowHJRs1$Uz(U5czu1MpPuehm&_+!mh
zBCK+|!mIx~*aZGQuF+kD9sw_7V0znMB!-SA%5Rf`7z8&l6q0ge6}s;Fn=4=3-P2eh3-t>smUhK&Z9Y*ECY6o
zFp{8o?6ww*;A@0J##$U|yAqr{e@iXf;^-kSZ!t#a;_C)$MPJ--Y>QKB6JfJOBsbJu
zt5ofln&2DIuH#2K&vM#uJZLh&KCtHgN$P&i!SS5qRMrh&pg8=9;UOjcY_Os=P0W*i
z2Q=LsO(*Yds%xhrf-t(c1OA#=Y98@ogC>4#8
literal 0
HcmV?d00001
diff --git a/static/comment-empty.png b/static/comment-empty.png
new file mode 100644
index 0000000000000000000000000000000000000000..810a3b1c00626a828b3102dd113d7336218588f6
GIT binary patch
literal 5603
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
W$rPvHafW|CYfKC+4L<9+Cj1}5&Fp9Z
literal 0
HcmV?d00001
diff --git a/static/coupon-empty.png b/static/coupon-empty.png
new file mode 100644
index 0000000000000000000000000000000000000000..9e14d9d1907a882ea19c88e4d7392f94c4a20213
GIT binary patch
literal 5245
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