Jelajahi Sumber

2023.10.20
- 初始化项目

zweiqin 1 tahun lalu
melakukan
a4ab99cb75
100 mengubah file dengan 13975 tambahan dan 0 penghapusan
  1. 26 0
      .gitignore
  2. 767 0
      App.vue
  3. 2 0
      Dockerfile
  4. 37 0
      api/convenient-services/index.js
  5. 46 0
      api/home/index.js
  6. 121 0
      api/user/index.js
  7. 24 0
      api/user/ledger.js
  8. 274 0
      component/share.vue
  9. 52 0
      components/BeeAvatar/BeeAvatar.vue
  10. 74 0
      components/BeeBack/BeeBack.vue
  11. 210 0
      components/BeeBrandPane/BeeBrandPane.vue
  12. TEMPAT SAMPAH
      components/BeeBrandPane/images/ear.png
  13. TEMPAT SAMPAH
      components/BeeBrandPane/images/location.png
  14. TEMPAT SAMPAH
      components/BeeBrandPane/images/star.png
  15. TEMPAT SAMPAH
      components/BeeBrandPane/images/tag1.png
  16. TEMPAT SAMPAH
      components/BeeBrandPane/images/to.png
  17. 67 0
      components/BeeIcon/BeeIcon.vue
  18. 53 0
      components/BeeLocale/BeeLocale.vue
  19. TEMPAT SAMPAH
      components/BeeLocale/locale.png
  20. 103 0
      components/BeeMenus/BeeMenus.vue
  21. 194 0
      components/BeeStoreFilter/BeeStoreFilter.vue
  22. TEMPAT SAMPAH
      components/BeeStoreFilter/image/code.png
  23. TEMPAT SAMPAH
      components/BeeStoreFilter/image/filter.png
  24. 207 0
      components/BrandCouponList/BrandCouponList.vue
  25. 144 0
      components/BrandGoods/BrandGoods.vue
  26. 237 0
      components/BrandGoodsList/BrandGoodsList.vue
  27. 312 0
      components/CashierList/CashierList.vue
  28. 23 0
      components/CategoryIcon/CategoryIcon.vue
  29. 79 0
      components/DefaultHead/index.vue
  30. 153 0
      components/DragButton/DragButton.vue
  31. 68 0
      components/Empty/index.vue
  32. 46 0
      components/JAvatar/JAvatar.vue
  33. 42 0
      components/JBack/JBack.vue
  34. 54 0
      components/JHeader/JHeader.vue
  35. 113 0
      components/JRedEnvelope/JRedEnvelope.vue
  36. 155 0
      components/ListBottomTips/index.vue
  37. 103 0
      components/LoadMore/LoadMore.vue
  38. 257 0
      components/Loading/index.vue
  39. 126 0
      components/NewGoods/NewGoods.vue
  40. 49 0
      components/NoMore/NoMore.vue
  41. 59 0
      components/OrderInfo/OrderInfo.vue
  42. 101 0
      components/SearchBar/SearchBar.vue
  43. TEMPAT SAMPAH
      components/SearchBar/search.png
  44. 232 0
      components/Skeleton/index.vue
  45. 143 0
      components/StoreGoods/StoreGoods.vue
  46. 92 0
      components/StoreGoodsList/StoreGoodsList.vue
  47. 127 0
      components/VoucherUse/VoucherUse.vue
  48. 672 0
      components/activities/combinedSales.vue
  49. 411 0
      components/adWindow.vue
  50. 85 0
      components/basics/categoryList.vue
  51. 332 0
      components/basics/categoryShow.vue
  52. 80 0
      components/basics/components/ProductSkeleton.vue
  53. 24 0
      components/canvasShow/basics/assistDiv.vue
  54. 121 0
      components/canvasShow/basics/banner.vue
  55. 131 0
      components/canvasShow/basics/brandList.vue
  56. 109 0
      components/canvasShow/basics/categoryList.vue
  57. 113 0
      components/canvasShow/basics/coupon/app/index.vue
  58. 111 0
      components/canvasShow/basics/coupon/mixin.js
  59. 375 0
      components/canvasShow/basics/coupon/pc/index.vue
  60. 238 0
      components/canvasShow/basics/custom.vue
  61. 300 0
      components/canvasShow/basics/discount/app/index.vue
  62. 110 0
      components/canvasShow/basics/discount/mixin.js
  63. 247 0
      components/canvasShow/basics/discount/pc/index.vue
  64. 213 0
      components/canvasShow/basics/group/app/index.vue
  65. 86 0
      components/canvasShow/basics/group/mixin.js
  66. 250 0
      components/canvasShow/basics/group/pc/index.vue
  67. 131 0
      components/canvasShow/basics/header/app/index.vue
  68. 47 0
      components/canvasShow/basics/header/mixin.js
  69. 119 0
      components/canvasShow/basics/imageText.vue
  70. 136 0
      components/canvasShow/basics/imageTextList.vue
  71. 101 0
      components/canvasShow/basics/imageTextNav.vue
  72. 71 0
      components/canvasShow/basics/live/app/index.vue
  73. 483 0
      components/canvasShow/basics/live/app/item.vue
  74. 61 0
      components/canvasShow/basics/live/mixin.js
  75. 228 0
      components/canvasShow/basics/newProduct/app/index.vue
  76. 89 0
      components/canvasShow/basics/newProduct/mixin.js
  77. 96 0
      components/canvasShow/basics/notice.vue
  78. 218 0
      components/canvasShow/basics/price/app/index.vue
  79. 73 0
      components/canvasShow/basics/price/mixin.js
  80. 253 0
      components/canvasShow/basics/price/pc/index.vue
  81. 416 0
      components/canvasShow/basics/product/app/index.vue
  82. 116 0
      components/canvasShow/basics/product/mixin.js
  83. 226 0
      components/canvasShow/basics/product/pc/index.vue
  84. 156 0
      components/canvasShow/basics/shop.vue
  85. 168 0
      components/canvasShow/basics/spike/app/index.vue
  86. 140 0
      components/canvasShow/basics/spike/mixin.js
  87. 211 0
      components/canvasShow/basics/spike/pc/index.vue
  88. 79 0
      components/canvasShow/basics/text.vue
  89. 125 0
      components/canvasShow/basics/video.vue
  90. 319 0
      components/canvasShow/basics/vip/app/index.vue
  91. 60 0
      components/canvasShow/basics/vip/mixin.js
  92. 241 0
      components/canvasShow/basics/vip/pc/index.vue
  93. 204 0
      components/canvasShow/canvasShowPage.vue
  94. 32 0
      components/canvasShow/config/api.js
  95. 13 0
      components/canvasShow/config/config.js
  96. 201 0
      components/canvasShow/config/mixin/funMixin.js
  97. 9 0
      components/canvasShow/config/mixin/index.js
  98. 41 0
      components/canvasShow/config/mixin/sendReqMixin.js
  99. 132 0
      components/canvasShow/config/mixin/server.js
  100. TEMPAT SAMPAH
      components/canvasShow/static/images/btn-next.png

+ 26 - 0
.gitignore

@@ -0,0 +1,26 @@
+.DS_Storep
+.DS_Store
+# Editor directories and files
+.idea
+.vscode
+.hbuilderx
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.hbuilderx
+/unpackage/dist/dev
+/unpackage/dist/static
+/unpackage/dist/build
+/unpackage/dist/build/mp-alipay/
+/unpackage/dist/build/mp-weixin/
+/unpackage/dist/build/app-plus/
+/unpackage/dist/build/.automator
+/unpackage/cache/
+/unpackage/cache/apk
+/unpackage/release
+/unpackage/res
+/unpackage/resources
+/node_modules
+package-lock.json
+

+ 767 - 0
App.vue

@@ -0,0 +1,767 @@
+<script>
+// // #ifdef MP-WEIXIN
+// const miniShopPlugin = requirePlugin('mini-shop-plugin');
+// // #endif
+import NET from '@/utils/request'
+import API from '@/config/api'
+import { J_STORAGE_KEY } from './config/constant'
+
+export default {
+	name: 'App',
+	globalData: {
+		isIphone: false
+	},
+	onShow() {
+		this.$store.dispatch('location/getCurrentLocation', (res) => {})
+	},
+	onLaunch(options) {
+		if (options && options.path === 'pages_category_page1/goodsModule/goodsDetails' && options.query) {
+			this.globalData.productShareItem = options.query
+		}
+		if (options && options.path === 'pages_category_page1/store/index' && options.query) {
+			this.globalData.shopShareItem = options.query
+		}
+		if (options && options.path === 'pages_category_page1/distributionModule/recruit' && options.query) {
+			this.globalData.distributeRecruitItem = options.query
+		}
+		if (options && options.path === 'pages_category_page1/goodsModule/inviteSpell' && options.query) {
+			this.globalData.inviteSpellShareItem = options.query
+		}
+		// 判断设备是否为 iPhone
+		const self = this
+		uni.getSystemInfo({
+			success(res) {
+				if (res.safeArea.top > 20 && res.model.indexOf('iPhone') !== -1) {
+					self.globalData.isIphone = true
+				}
+			}
+		})
+		// 购物车右上角数量
+		if (uni.getStorageSync(J_STORAGE_KEY)) {
+			NET.request(API.ShoppingCart, {}, 'GET').then((resCart) => {
+				let cartNum = 0
+				resCart.data.forEach((shopItem) => {
+					shopItem.skus.forEach((goodsItem) => {
+						cartNum += goodsItem.number
+					})
+				})
+				uni.setStorageSync('allCartNum', cartNum)
+				if (cartNum > 0) {
+					uni.setTabBarBadge({
+						index: 3,
+						text: cartNum.toString()
+					})
+				}
+			})
+		}
+	}
+
+}
+
+</script>
+
+<style lang="scss">
+@keyframes loading {
+  0%{
+    background: #e7e7e7;
+  }
+  50%{
+    background: #f8f8f8;
+  }
+  100%{
+    background: #e7e7e7;
+  }
+}
+// 自定义骨架屏
+.ske-loading{
+  .child-loading{
+    animation: loading 2s linear 0s infinite alternate;
+  }
+}
+/*每个页面公共css */
+@import "uview-ui/index.scss";
+
+::-webkit-scrollbar {
+  width: 0 !important;
+  height: 0 !important;
+}
+
+page {
+  height: 100%;
+}
+
+uni-rich-text img {
+  max-width: 100% !important;
+}
+// 图片占位图
+.pic-img{
+	width: 100%;
+	height: 100%;
+}
+.default-img {
+	background: url('./static/images/origin/default.png') no-repeat center;
+	background-size: 100% 100%;
+}
+
+.line1 {
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  overflow: hidden;
+}
+
+.wid {
+  width: 100%;
+}
+
+.fs4 {
+  font-size: 4upx;
+}
+
+.fs18 {
+  font-size: 18upx;
+}
+
+.fs20 {
+  font-size: 20upx;
+}
+
+.fs22 {
+  font-size: 22upx;
+}
+
+.fs24 {
+  font-size: 24upx;
+}
+
+.fs26 {
+  font-size: 26upx;
+}
+
+.fs28 {
+  font-size: 28upx;
+}
+
+.fs30 {
+  font-size: 30upx;
+}
+
+.fs32 {
+  font-size: 32upx;
+}
+
+.fs34 {
+  font-size: 34upx;
+}
+
+.fs36 {
+  font-size: 36upx;
+}
+
+.fs38 {
+  font-size: 38upx;
+}
+
+.fs40 {
+  font-size: 40upx;
+}
+
+.fs42 {
+  font-size: 42upx;
+}
+
+.fs44 {
+  font-size: 44upx;
+}
+
+.fs46 {
+  font-size: 46upx;
+}
+
+.fs48 {
+  font-size: 46upx;
+}
+
+.fs50 {
+  font-size: 50upx;
+}
+
+.fs60 {
+  font-size: 60upx;
+}
+
+.fs-bold {
+  font-weight: bold;
+}
+
+.fs-weight-300 {
+  font-weight: 300;
+}
+
+.fs-weight-200 {
+  font-weight: 200;
+}
+
+.fs-weight-400 {
+  font-weight: 400;
+}
+
+.flex-display {
+  display: flex;
+}
+
+.flex-center {
+  display: flex;
+  justify-content: center;
+}
+
+.flex-items {
+  display: flex;
+  align-items: center;
+}
+
+.flex-items-plus {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.flex-start {
+  display: flex;
+  justify-content: flex-start;
+}
+
+.flex-end {
+  display: flex;
+  justify-content: flex-end;
+}
+
+.flex-end-plus {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+}
+
+.flex-column {
+  flex-direction: column
+}
+
+.flex-column-plus {
+  display: flex;
+  flex-direction: column
+}
+
+.flex-row {
+  flex-direction: row
+}
+
+.flex-row-plus {
+  display: flex;
+  flex-direction: row
+}
+
+.flex-sp-around {
+  justify-content: space-around;
+}
+
+.flex-sp-between {
+  justify-content: space-between;
+}
+
+.text-align {
+  text-align: center;
+}
+
+.flex-wrap-1 {
+  display: flex;
+  flex-wrap: wrap
+}
+
+.flex-nowrap-1 {
+  display: flex;
+  flex-wrap: nowrap
+}
+
+.align-end {
+  display: flex;
+  align-items: flex-end;
+}
+
+.align-sp-between {
+  align-content: space-between;
+}
+
+.mar-top-5 {
+  margin-top: 5upx;
+}
+
+.mar-top-10 {
+  margin-top: 10upx;
+}
+
+.mar-top-20 {
+  margin-top: 20upx;
+}
+
+.mar-top-30 {
+  margin-top: 30upx;
+}
+
+.mar-top-32 {
+  margin-top: 32upx;
+}
+
+.mar-top-36 {
+  margin-top: 36upx;
+}
+
+.mar-top-40 {
+  margin-top: 40upx;
+}
+
+.mar-top-50 {
+  margin-top: 50upx;
+}
+
+.mar-top-60 {
+  margin-top: 60upx;
+}
+
+.mar-top-70 {
+  margin-top: 70upx;
+}
+
+.mar-top-100 {
+  margin-top: 100upx;
+}
+
+.mar-top-percent40 {
+  margin-top: 40%;
+}
+
+.mar-top-half {
+  margin-top: 50%;
+}
+
+.mar-left-6 {
+  margin-left: 6upx;
+}
+
+.mar-left-5 {
+  margin-left: 5upx;
+}
+
+.mar-left-10 {
+  margin-left: 10upx;
+}
+
+.mar-left-20 {
+  margin-left: 20upx;
+}
+
+.mar-left-30 {
+  margin-left: 30upx;
+}
+
+.mar-left-35 {
+  margin-left: 35upx;
+}
+
+.mar-left-40 {
+  margin-left: 40upx;
+}
+
+.mar-left-50 {
+  margin-left: 50upx;
+}
+
+.mar-left-60 {
+  margin-left: 60upx;
+}
+
+.mar-left-70 {
+  margin-left: 70upx;
+}
+
+.mar-right-10 {
+  margin-right: 10upx;
+}
+
+.mar-right-20 {
+  margin-right: 20upx;
+}
+
+.mar-right-25 {
+  margin-right: 25upx;
+}
+
+.mar-right-30 {
+  margin-right: 30upx;
+}
+
+.mar-right-35 {
+  margin-right: 35upx;
+}
+
+.mar-right-40 {
+  margin-right: 40upx;
+}
+
+.mar-right-50 {
+  margin-right: 50upx;
+}
+
+.pad-left-10 {
+  padding-left: 10upx;
+}
+
+.pad-left-20 {
+  padding-left: 20upx;
+}
+
+.pad-left-40 {
+  padding-left: 40upx;
+}
+
+.pad-right-20 {
+  padding-right: 20upx;
+}
+
+.pad-top-20 {
+  padding-top: 20upx;
+}
+
+.pad-top-40 {
+  padding-top: 40upx;
+}
+
+.pad-bot-20 {
+  padding-bottom: 20upx;
+}
+
+.pad-topbot-20 {
+  padding: 20upx 0upx;
+}
+
+.pad-topbot-5 {
+  padding: 0upx 5upx;
+}
+
+.pad-topbot-10 {
+  padding: 0upx 10upx;
+}
+
+.pad-topbot-50 {
+  padding: 50upx 0upx;
+}
+
+.pad-bot-20 {
+  padding-bottom: 20upx;
+}
+
+.pad-bot-30 {
+  padding-bottom: 30upx;
+}
+
+.pad-bot-40 {
+  padding-bottom: 40upx;
+}
+
+.pad-bot-100 {
+  padding-bottom: 100upx;
+}
+
+.pad-bot-140 {
+  padding-bottom: 140upx;
+}
+
+.bor-rad-30 {
+  border-radius: 30upx;
+}
+
+.bor-rad-45 {
+  border-radius: 45upx;
+}
+
+.bor-rad-half {
+  border-radius: 50%;
+}
+
+.backColor {
+  background-color: #009688;
+}
+
+.backColorFFF {
+  background-color: #FFFFFF;
+}
+
+.pos-abs {
+  position: absolute;
+}
+
+.bor-bot-line {
+  border-bottom: #C8C7CC 1upx solid;
+}
+
+.bor-line-F7F7F7 {
+  border-bottom: #F7F7F7 1upx solid;
+}
+
+.bor-line-E5E5E5 {
+  border-bottom: #E5E5E5 1upx solid;
+}
+
+.borRig-line-E5E5E5 {
+  border-right: #DDDDDD 2upx solid;
+}
+
+.borRig-line-20 {
+  border-bottom: #F7F7F7 20upx solid;
+}
+
+.font-color-red {
+  color: red;
+}
+
+.font-color-FFF {
+  color: #FFFFFF;
+}
+
+.font-color-8A734A {
+  color: #8A734A;
+}
+
+.font-color-71521B {
+  color: #71521B;
+}
+
+.font-color-222 {
+  color: #222222;
+}
+
+.font-color-333 {
+  color: #333333;
+}
+
+.font-color-666 {
+  color: #666666;
+}
+
+.font-color-999 {
+  color: #999999;
+}
+
+.font-color-656 {
+  color: #656565;
+}
+
+.font-color-DDD {
+  color: #DDDDDD;
+}
+
+.font-color-CCC {
+  color: #CCCCCC;
+}
+
+.font-color-FFEBC4 {
+  color: #FFEBC4;
+}
+
+.font-color-1CC363 {
+  color: #1CC363;
+}
+
+.font-color-47A7EE {
+  color: #47A7EE;
+}
+
+.font-color-C5AA7B {
+  color: #C5AA7B;
+}
+
+.font-color-FF7700 {
+  color: #FF7700;
+}
+
+.font-color-FF7911 {
+  color: #FF7911;
+}
+
+.font-color-80 {
+  color: #808080;
+}
+
+.font-color-DD {
+  color: #DD524D;
+}
+
+.font-color-C83732 {
+  color: #C83732;
+}
+
+.font-color-3F {
+  color: #3F536E;
+}
+
+.font-color-009 {
+  color: #009688;
+}
+
+.font-weight-500 {
+  font-weight: 500;
+}
+
+.font-weight-bold {
+  font-weight: bold;
+}
+
+.overflow {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.overflowNoDot {
+  display: block;
+  overflow: hidden;
+}
+
+.discountsPriceLine {
+  text-decoration: line-through;
+}
+
+.border-bottom-Line {
+  border-bottom: 1upx solid #EDEDED;
+}
+
+.decoration {
+  text-decoration: line-through;
+}
+
+.anonymous {
+  margin-top: 25upx;
+
+  .uni-checkbox-input {
+    border-color: #C5AA7B !important;
+    width: 30upx;
+    height: 30upx;
+  }
+
+  .uni-checkbox-input-checked:before {
+    font-size: 30upx !important;
+  }
+
+  .uni-checkbox-input-checked {
+    background: #C5AA7B;
+  }
+}
+
+.footprint {
+  .itemList {
+    .uni-checkbox-input {
+      border-color: #C5AA7B !important;
+      width: 36upx;
+      height: 36upx;
+      border-radius: 50%;
+      margin-right: 20upx;
+    }
+
+    .uni-checkbox-input-checked:before {
+      font-size: 36upx !important;
+    }
+
+    .uni-checkbox-input-checked {
+      background: #C5AA7B;
+    }
+  }
+}
+
+.itemInfo {
+  uni-slider {
+    margin: 0;
+
+    .uni-slider-thumb {
+      display: none;
+    }
+
+    .uni-slider-handle-wrapper {
+      height: 18upx;
+      border-radius: 0;
+      border: 1upx solid #FF736C;
+    }
+
+    .uni-slider-track {
+      border-radius: 0;
+    }
+
+    .uni-slider-tap-area {
+      flex: 0 0 70%;
+      padding: 0;
+    }
+  }
+}
+
+.uni-modal {
+  padding: 20rpx;
+  box-sizing: border-box;
+}
+
+uni-modal .uni-modal__ft:after {
+  border-top: none;
+}
+
+uni-modal .uni-modal__btn {
+  color: #333333;
+  border: 2rpx solid #333333;
+  font-weight: 400;
+  margin: 0 10rpx;
+  font-size: 28rpx;
+}
+
+.uni-tabbar .uni-tabbar__reddot {
+  background: #C5AA7B;
+  color: #FFFFFF;
+}
+
+uni-checkbox:not([disabled]) .uni-checkbox-input:hover {
+  border-color: #C5AA7B;
+}
+
+.u-arrow {
+  display: inline-block;
+  width: 20rpx;
+  height: 20rpx;
+  border-top: 1rpx solid #999;
+  border-right: 1rpx solid #999;
+}
+
+.u-arrow-up {
+  transform: rotate(-45deg);
+}
+
+.u-arrow-down {
+  transform: rotate(135deg);
+}
+
+.u-arrow-left {
+  transform: rotate(-135deg);
+}
+
+.u-arrow-right {
+  transform: rotate(45deg);
+}
+
+.uni-picker-container .uni-picker-action.uni-picker-action-confirm {
+  color: #C5AA7B;
+}
+
+.u-drawer-content {
+  //border-radius: 0 !important;
+}
+</style>
+
+<style>
+.uni-modal__btn_primary {
+  background: #333333;
+  color: #FFEBC4 !important;
+}
+</style>

+ 2 - 0
Dockerfile

@@ -0,0 +1,2 @@
+FROM registry.cn-guangdong.aliyuncs.com/shop/nginx
+COPY unpackage/dist/build/h5/ /home/ui-h5/

+ 37 - 0
api/convenient-services/index.js

@@ -0,0 +1,37 @@
+import { MainRequest } from '@/utils'
+
+/**
+ *
+ * @param {个人寄快递} data
+ * @returns
+ */
+
+// 获取快递公司列表
+export const getKuaiDiCorporation = (data) => MainRequest('/kuaidi100/getCompanies', data, 'get')
+
+// 查询跟踪快递状态
+export const getKuaiDiMethods = (data) => MainRequest('/kuaidi100/queryTrack', data, 'get')
+
+// 查询我个人寄快递的记录                     测试数据,最终将以USERID为准     672950279
+export const getBianminRecordKuaidiApi = (data) => MainRequest('/dtsBianminRecord', data, 'get')
+
+// 查询我个人寄快递记录的消息事件
+export const getKuaiDiRecordMsg = (data) => MainRequest('/dtsBianminRecordMsg', data, 'get')
+
+// 可以寄快递的公司编码
+export const getKuaidi100ComApi = (data) => MainRequest('/kuaidi100/kuaidicom', data, 'get')
+
+// C端寄件下单-价格查询
+export const getKuaidi100PriceApi = (data) => MainRequest('/kuaidi100/cloud/corderPrice', data)
+
+// C端寄件下单
+export const addKuaidi100CorderApi = (data) => MainRequest('/kuaidi100/cloud/corder', data)
+
+// // C端寄件下单-回调url
+// export const orderCancelApi = (data) => KuaiDiRequest('/kuaidi100/corderCb', data)
+
+// C端寄件下单-取消
+export const orderCancelApi = (data) => MainRequest('/kuaidi100/cloud/corderCancel', data)
+
+// // C端寄件下单-快递信息推送
+// export const orderCancelApi = (data) => KuaiDiRequest('/kuaidi100/corderTrackCb', data)

+ 46 - 0
api/home/index.js

@@ -0,0 +1,46 @@
+import { MainRequest } from '../../utils'
+
+/**
+ * 获取首页商品列表
+ * @returns
+ */
+
+export const getIndexDataApi = (data) => MainRequest('/xxxx/xxx', data)
+
+/**
+ * @description 获取分类
+ * @param {
+ *  id  {String} 二级分类类目,
+ *  goodsType {String}  1-家具 2-材料
+ * } data
+ * @returns
+ */
+
+export const getGoodsTypesApi = (data) => MainRequest('/xxxx/xxx', data)
+
+/**
+ * @description 获取当前分类下的二级类目
+ */
+
+export const getTypeDetailList = (data) => MainRequest('/xxxx/xxx', data)
+
+/**
+ *@exports 根据id查询商品
+ * @param {*} data
+ * @returns
+ */
+
+export const getUserCouponApi = (data) => MainRequest('/xxxx/xxx', data)
+
+export const getAllCategoryList = (data) => MainRequest('/xxxx/xxx', data)
+
+export const getApponitGoodsApi = (data) => MainRequest('/xxxx/xxx', data)
+
+/**
+ *@exports 根据关键字查询商品
+ * @param {*} data
+ * @returns
+ */
+
+export const getDtsBrandGoods = (data) => MainRequest('/xxxx/xxx', data, 'POST')
+

+ 121 - 0
api/user/index.js

@@ -0,0 +1,121 @@
+import { MainRequest } from '../../utils'
+
+/**
+ * 原来支付相关
+ */
+
+// 查询花呗手续费配置
+export const getOrderHuabeiConfigApi = (data) => MainRequest('/order/getHuabeiConfig', data)
+
+//  APP立即支付
+export const gotoOrderAppPayApi = (data) => MainRequest('/order/gotoAppPay', data, 'POST')
+
+// 商城订单-通联H5支付
+export const gotoOrderH5PayApi = (data) => MainRequest('/order/gotoH5Pay', data, 'POST')
+
+// 小程序支付
+export const gotoOrderPayApi = (data) => MainRequest('/order/gotoPay', data, 'POST')
+
+// 小程序支付成功
+export const payOrderSuccessApi = (data) => MainRequest('/order/paySuccess', data, 'POST')
+
+/**
+ * 原来订单相关
+ */
+
+// 订单详情查询
+export const getOrderDetailApi = (data) => MainRequest('/order/getById', data)
+
+/**
+ * 原来信息相关
+ */
+
+// 我的账户信息查询
+export const getDistributorApi = (data) => MainRequest('/distributor/getDistributor', data)
+
+// 服务协议-查询分类层级
+export const getQueryDictByNameApi = (data) => MainRequest('/dict/getByName', data)
+
+// 微信登录
+export const updateWXLoginApi = (data) => MainRequest('/app/wxLogin', data, 'POST')
+
+// APP微信登录
+export const updateWXAppLoginApi = (data) => MainRequest('/app/wxAppLogin', data, 'POST')
+
+// 获取短信验证码
+export const getVerifyCodeApi = (data) => MainRequest('/app/getCode', data)
+
+/**
+ * 申请记录
+ */
+
+// 用户商家申请记录查询
+export const getApplyByStoreListApi = (data) => MainRequest('/check/getApplyByUserId', data)
+
+// 用户分公司与策划师申请记录查询
+export const getApplyByUserListApi = (data) => MainRequest('/check/getApplyByUserId2', data)
+
+// 套餐购买提交订单
+export const paySubmitSettleApi = (data) => MainRequest('/order/submitSettle', data, 'POST')
+
+// // H5入驻购买支付
+// export const payGotoH5SettlePayApi = (data) => MainRequest('/order/gotoH5SettlePay', data, 'POST')
+
+/**
+ * 代金券
+ */
+
+// 代金券购买提交订单
+export const paySubmitVoucherApi = (data) => MainRequest('/order/submitVoucher', data, 'POST')
+
+// // H5代金券购买支付
+// export const payGotoH5VoucherApi = (data) => MainRequest('/order/gotoH5Voucher', data, 'POST')
+
+/**
+ * 地图红包
+ */
+
+// 查询商家范围内地图红包
+export const getWrapRedReleaseApi = (data) => MainRequest('/wrapRed/releaseWrap', data, 'POST')
+
+// 用户领取红包
+export const addWrapRedReceiveApi = (data) => MainRequest('/wrapRed/receiveWrap', data, 'POST')
+
+// 用户或商家发布红包
+export const addWrapRedReleaseApi = (data) => MainRequest('/wrapRed/release', data, 'POST')
+
+// TODO查询我的发布红包
+export const getWrapRedselectApi = (data) => MainRequest('/wrapRed/selectWrap', data)
+
+/**
+ * 商家优惠券
+ */
+
+// 查询商家的优惠卷
+export const getBrandCouponListApi = (data) => MainRequest('/wrapRed/selectWrapId', data)
+
+/**
+ * 本地生活
+ */
+
+// 查询店铺分类
+export const getCategoryListApi = (data) => MainRequest('/shopCategory/getShopCategoryLevel', data)
+
+// 查询店铺父级分类查询子级分类
+export const getSubMenuByPidApi = (data) => MainRequest('/shopCategory/getShopCategorySon', data)
+
+// 附近商家
+export const getHomeBrandListApi = (data) => MainRequest('/shop/getShopAll', data, 'POST')
+
+// 生成用户订单的核销码
+export const getOrderVerificationHxCodeApi = (data) => MainRequest('/orderVerificationCode/getHxCode', data)
+
+// 核销订单
+export const updateSetHxCodeApi = (data) => MainRequest('/order/write', data, 'POST')
+
+/**
+ * 本地生活
+ */
+
+// 用户扫码绑定
+export const bindPlatformInfoCodeApi = (data) => MainRequest('/platformInfoCode/binding', data, 'POST')

+ 24 - 0
api/user/ledger.js

@@ -0,0 +1,24 @@
+import { MainRequest } from '../../utils'
+
+// /user/getPlatformShop 获取绑定的商家信息
+/*
+data: {
+	platformUserId
+}
+*/
+export const getPlatformShop = (params) => MainRequest('/user/getPlatformShop', params)
+
+// /user/getPlatformUser 绑定的策划师或分公司角色信息
+export const getPlatformUser = (params) => MainRequest('/user/getPlatformUser', params)
+
+// /user/getCommission 用户佣金统计+记录
+export const getCommission = (params) => MainRequest('/user/getCommission', params)
+
+// /user/getUserList 用户粉丝统计+列表
+export const getUserList = (params) => MainRequest('/user/getUserList', params)
+
+// /user/getInfoCode  获取邀请码
+export const getInfoCode = (params) => MainRequest('/user/getInfoCode', params)
+
+// /voucherShopHold/transfer  代金券转赠
+export const transfer = (data) => MainRequest('/voucherShopHold/transfer', data, 'POST')

+ 274 - 0
component/share.vue

@@ -0,0 +1,274 @@
+<template>
+	<div>
+		<u-popup v-model="shareShow" :round="10" mode="bottom" @close="cancel(1)">
+			<view class="share">
+				<!-- <u-mask :show="true" class="flex-items-plus flex-row"> -->
+				<text class="h3">邀请好友</text>
+				<view class="share-list">
+					<view class="ul">
+						<!-- #ifdef APP-PLUS -->
+						<view class="li" @click="share('weixin')">
+							<image class="icon" src="../static/images/origin/weixin2x.png"></image>
+							<label class="label">微信</label>
+						</view>
+						<view class="li" @click="share('weixinpyq')">
+							<image class="icon" src="../static/images/origin/pyq.png"></image>
+							<label class="label">朋友圈</label>
+						</view>
+						<!-- #endif -->
+						<!-- #ifdef MP-WEIXIN -->
+						<view class="li">
+							<button open-type="share" :data-obj="wxShareData" class="share-button" @share="onShareAppMessage">
+								<image class="icon" src="../static/images/origin/forward.png"></image>
+								<label class="label">好友</label>
+							</button>
+						</view>
+						<view class="li" @click="share('weixinpyq')">
+							<image class="icon" src="../static/images/origin/pyq.png"></image>
+							<label class="label">朋友圈</label>
+						</view>
+						<!-- #endif -->
+						<view class="li" @click="share('lianjie')">
+							<image class="icon" src="../static/images/origin/lianjie2.png"></image>
+							<label class="label">链接</label>
+						</view>
+					</view>
+				</view>
+				<view class="btn-close" @click="cancel(1)">取消</view>
+			</view>
+		</u-popup>
+		<u-popup v-model="wapShow" :round="10" mode="bottom" :border-radius="10" @close="cancel(2)">
+			<view class="share-h5">
+				<view class="text">
+					点击浏览器下方
+					<view class="icon">
+						<u-icon name="list" color="#fff" size="28"></u-icon>
+					</view>
+					即可进行分享
+				</view>
+			</view>
+		</u-popup>
+	</div>
+</template>
+
+<script>
+import ClipboardJS from 'clipboard'
+import UImage from '../uview-ui/components/u-image/u-image'
+
+const API = require('../config/api')
+export default {
+	name: 'Share',
+	components: { UImage },
+	props: {
+		img: {
+			type: String,
+			default: ''
+		},
+		url: {
+			type: String,
+			dafault: ''
+		},
+		title: {
+			type: String,
+			dafault: ''
+		},
+		urlParms: {
+			type: String,
+			default: ''
+		}
+	},
+	onShareAppMessage(e) {
+		if (e.from == 'button') {
+			// 点击button
+		}
+		if (e.from == 'menu') {
+			// 点击右上角按钮
+		}
+		// 获取按钮传进来的参数 data 中的item值
+		const params = e.target.dataset.obj// 获取的为 data 中定义的item值
+		return {
+			path: `/pages_category_page1/goodsModule/inviteSpell?${params.url}`
+		}
+	},
+	data() {
+		return {
+			shareShow: false,
+			wapShow: false,
+			wxShareData: {
+				url: ''
+			}
+		}
+	},
+	computed: {
+		longUrl() {
+			return API.shareLink + '/#' + this.url
+		}
+	},
+	mounted() {
+		this.wxShareData = this.url
+	},
+	methods: {
+		cancel(key) {
+			if (key === 1) {
+				this.shareShow = false
+				this.$emit('shareCancel')
+			} else if (key === 2) {
+				this.wapShow = false
+			}
+		},
+		wxShare(type) {
+			// #ifdef APP
+			uni.share({
+				provider: 'weixin',
+				type: 0,
+				title: this.title,
+				scene: type, // WXSceneSession会话 WXSceneTimeline朋友圈
+				imageUrl: this.img,
+				href: this.longUrl,
+				success: () => {
+				},
+				fail: (err) => {
+					throw Error(err)
+				}
+			})
+			// #endif
+			// #ifdef MP-WEIXIN
+			uni.showToast({
+				title: '请点击右上角打开菜单进行朋友圈分享',
+				icon: 'none'
+			})
+			// #endif
+		},
+		share(key) {
+			switch (key) {
+				case 'weixin':
+					this.wxShare('WXSceneSession')
+					break
+				case 'weixinpyq':
+
+					this.wxShare('WXSceneTimeline')
+					break
+				case 'qq':
+
+					break
+				case 'weibo':
+
+					break
+				case 'lianjie':
+					uni.setClipboardData({
+						data: this.title + this.longUrl,
+						success: () => {
+							uni.showToast({
+								title: '复制成功'
+							})
+						}
+					})
+					break
+			}
+		},
+		// 重写分享方法
+		overShare() {
+			// 监听路由切换
+			// 间接实现全局设置分享内容
+			wx.onAppRoute(function (res) {
+				// 获取加载的页面
+				const pages = getCurrentPages()
+				// 获取当前页面的对象
+				const view = pages[pages.length - 1]
+				let data
+				if (view) {
+					data = view.data
+					if (!data.isOverShare) {
+						data.isOverShare = true
+						view.onShareAppMessage = function () {
+							// 你的分享配置
+							return {
+								title: '标题',
+								path: '/pages/nearby/index'
+							}
+						}
+					}
+				}
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+.share {
+	background-color: #F8F8F8;
+	text-align: center;
+
+	.h3 {
+		font-size: 30rpx;
+		color: #333333;
+		line-height: 42rpx;
+		padding: 30rpx 0;
+		border-bottom: 2px solid #F0F0F0;
+		display: block;
+	}
+
+	.share-list {
+		padding: 40rpx 0 54rpx;
+
+		.ul {
+			display: flex;
+			justify-content: space-around;
+
+			.li {
+				&::after {
+					border: none;
+				}
+
+				.icon {
+					display: block;
+					width: 92rpx;
+					height: 92rpx;
+				}
+
+				.label {
+					padding-top: 22rpx;
+					font-size: 24rpx;
+					line-height: 34rpx;
+					color: #333333;
+					display: block;
+				}
+			}
+		}
+	}
+
+	.btn-close {
+		background-color: #fff;
+		padding: 30rpx 0;
+		font-size: 26rpx;
+		color: #333;
+	}
+}
+
+.share-h5 {
+	padding: 0 20rpx;
+	display: flex;
+	justify-content: center;
+
+	.text {
+		line-height: 100rpx;
+		font-size: 30px;
+
+		.icon {
+			background-color: #333;
+			border-radius: 50%;
+			display: inline-block;
+			width: 40rpx;
+			height: 40rpx;
+			text-align: center;
+			line-height: 40rpx;
+			margin: 0 10rpx;
+		}
+	}
+}
+
+.share-button::after {
+	border: none;
+}
+</style>

+ 52 - 0
components/BeeAvatar/BeeAvatar.vue

@@ -0,0 +1,52 @@
+<template>
+	<tui-lazyload-img
+		class="image-wrapper" :width="width ? width * 2 + 'rpx' : size * 2 + 'rpx'"
+		:height="height ? height * 2 + 'rpx' : size * 2 + 'rpx'" :src="src" :radius="radius"
+		:background-color="backgroundColor" mode="scaleToFill" :disconnect="true"
+		@click="$emit('click')"
+	></tui-lazyload-img>
+</template>
+
+<script>
+export default {
+	name: 'BeeAvatar',
+	props: {
+		src: {
+			type: String,
+			default: ''
+		},
+
+		size: {
+			type: Number,
+			default: 80
+		},
+
+		width: {
+			type: Number
+		},
+
+		height: {
+			type: Number
+		},
+
+		radius: {
+			type: String,
+			default: '50%'
+		},
+		backgroundColor: {
+			type: String,
+			default: '#fff'
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.image-wrapper {
+	flex-shrink: 0;
+}
+
+image {
+	will-change: transform
+}
+</style>

+ 74 - 0
components/BeeBack/BeeBack.vue

@@ -0,0 +1,74 @@
+<template>
+	<view class="back-container" @click="handleBack">
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+// import { tabbarList } from '../../common/globalData'
+
+export default {
+	name: 'BeeBack',
+	props: {
+		url: {
+			type: String,
+			default: ''
+		},
+		tab: {
+			type: String,
+			default: ''
+		},
+		redirect: {
+			type: String,
+			default: ''
+		},
+		successCb: {
+			type: Function,
+			default: () => {}
+		}
+	},
+	methods: {
+		handleBack() {
+			if (this.tab) {
+				this.$switchTab(this.url)
+			} else if (this.redirect) {
+				uni.redirectTo({
+					url: this.url
+				})
+			} else if (this.url) {
+				uni.navigateTo({
+					url: this.url
+				})
+			}
+
+			const pages = getCurrentPages()
+			const pagesLength = pages.length
+			let backLevel = 1
+			if (pages.length === 1) {
+				this.$switchTab('/pages/tabbar/index/index')
+			} else {
+				const lastUrl = pages[pagesLength - 1].route + this.getParams(pages[pagesLength - 1].options)
+				for (
+					let index = pages.length - 1;
+					index > 0 && index < pages.length;
+					index--
+				) {
+					if (pages[index].route + this.getParams(pages[index].options) === lastUrl) {
+						backLevel += 1
+					} else {
+						break
+					}
+				}
+				uni.navigateBack({
+					delta: backLevel - 1,
+					success: this.successCb
+				})
+			}
+		},
+
+		getParams(options) {
+			return JSON.stringify(options)
+		}
+	}
+}
+</script>

+ 210 - 0
components/BeeBrandPane/BeeBrandPane.vue

@@ -0,0 +1,210 @@
+<template>
+	<view class="bee-brand-pane-container">
+		<view class="bee-brand-top" @click="$jump(`/pages_category_page1/store/index?storeId=${brandInfo.shop_id}`)">
+			<view class="left">
+				<BeeAvatar :src="common.seamingImgUrl(brandInfo.shop_logo)" radius="10upx"></BeeAvatar>
+				<!-- <view class="tag"> 惊喜价 </view> -->
+			</view>
+			<view class="middle">
+				<view class="brand-name hidden">{{ brandInfo.shop_name }}</view>
+				<!-- <view class="rate">
+					<view style="display: flex;align-items: center;">
+					<BeeIcon :size="12" :src="require('./images/star.png')"></BeeIcon>
+					<text class="rate-text">{{ brandInfo.merchantRating || '5.0' }}分</text>
+					<text class="sub-text">{{ brandInfo.brandLabel | formatTag }}</text>
+					<text class="rate-text">¥{{ brandInfo.perCapita || '0' }}/人</text>
+					<text>月售 {{ brandInfo.salesVolume || '0' }}</text>
+					</view>
+					<view style="color: #777777;">{{ brandInfo.browse || 0 }}浏览量</view>
+					</view> -->
+				<view v-if="brandInfo.brief" class="elva">
+					<view class="elva-text hidden">{{ brandInfo.brief || '--' }}</view>
+				</view>
+				<view class="location-wrapper">
+					<BeeIcon style="line-height: 100%;" :src="require('./images/location.png')" :size="14"></BeeIcon>
+					<view class="detail">
+						<text v-if="isPositioning" class="dis-container">{{ positioningText }}</text>
+						<view v-else style="display: flex;align-items: center;">
+							<text class="dis-container"> {{ typeof brandInfo.distance === 'number' ? (brandInfo.distance / 1000).toFixed(2) : 0 }} km </text>
+							<BeeIcon :src="require('./images/to.png')" :size="14"></BeeIcon>
+						</view>
+					</view>
+				</view>
+				<view style="display: flex;flex-wrap: wrap;">
+					<!-- v-if="brandInfo.voucherNum" -->
+					<view
+						style="width: fit-content;margin-top: 8upx;margin-right: 8upx;padding: 4upx 8upx;border: 0.25px solid #0d0e0d;border-radius: 12upx;font-size: 28upx;font-weight: bold;color: #0d0e0d;"
+					>
+						补贴代金券{{ brandInfo.voucher_return || 0 }}%
+					</view>
+					<view
+						v-if="brandInfo.is_voucher"
+						style="width: fit-content;margin-top: 8upx;margin-right: 8upx;padding: 4upx 8upx;border: 0.25px solid #51cc46;border-radius: 12upx;font-size: 28upx;font-weight: bold;color: #51cc46;"
+					>
+						支持代金券
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="bee-brand-bottom">
+			<slot></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+
+	filters: {
+		formatTag(value) {
+			return {
+				0: '美酒',
+				1: '美食',
+				2: '娱乐',
+				3: '好玩'
+			}[value] || ''
+		}
+	},
+	props: {
+		brandInfo: {
+			type: Object,
+			required: true
+		},
+		isPositioning: {
+			type: Boolean,
+			default: true
+		}
+	},
+
+	data() {
+		return {
+			positioningText: '定位中',
+			timer: null
+		}
+	},
+	watch: {
+		isPositioning: {
+			handler(val, oldVal) {
+				this.timer && clearInterval(this.timer)
+				if (val) {
+					this.timer = setInterval(() => {
+						if (this.positioningText.length === 9) {
+							this.positioningText = '定位中'
+						} else {
+							this.positioningText = this.positioningText + '.'
+						}
+					}, 500)
+				} else {
+				}
+			},
+			immediate: true
+		}
+	},
+	beforeDestroy() {
+		this.timer && clearInterval(this.timer)
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.hidden {
+	width: 374upx;
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+}
+
+.bee-brand-pane-container {
+	// width: 100%;
+	background-color: #fff;
+	padding: 24upx 20upx;
+	border-radius: 20upx;
+	margin-top: 16upx;
+	transition: all 350ms;
+
+	&:active {
+		background-color: #e9e9e9;
+	}
+
+	.bee-brand-top {
+		display: flex;
+		align-items: flex-start;
+		width: 100%;
+
+		.left {
+			display: flex;
+			align-items: center;
+			flex-direction: column;
+
+			.tag {
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				width: 104upx;
+				height: 38upx;
+				background: url('./images/tag1.png');
+				background-size: cover;
+				color: #cb511e;
+				font-size: 24upx;
+				margin-top: 52upx;
+			}
+		}
+
+		.middle {
+			flex: 1;
+			margin: 0 28upx;
+
+			.brand-name {
+				color: #000;
+				font-size: 32upx;
+				font-weight: bold;
+			}
+
+			.rate {
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+				margin-top: 8upx;
+				font-size: 26upx;
+
+				text {
+					font-size: 24upx;
+					margin-left: 8upx;
+
+					&.rate-text {
+						color: #fa5151;
+					}
+				}
+			}
+
+			.location-wrapper {
+				display: flex;
+				align-items: center;
+				margin: 12upx 0 8upx;
+
+				// /deep/ .icon-container {
+				//   margin-top: 6upx;
+				// }
+
+				.detail {
+					background-color: #efefef;
+					// vertical-align: text-bottom;
+					padding: 0 4upx;
+					border-radius: 4upx;
+					margin-left: 4upx;
+				}
+			}
+
+			.elva {
+				margin: 8upx 0;
+
+				.elva-text {
+					// width: 348upx;
+					margin-left: 10upx;
+					font-size: 30upx;
+				}
+			}
+		}
+	}
+}
+</style>

TEMPAT SAMPAH
components/BeeBrandPane/images/ear.png


TEMPAT SAMPAH
components/BeeBrandPane/images/location.png


TEMPAT SAMPAH
components/BeeBrandPane/images/star.png


TEMPAT SAMPAH
components/BeeBrandPane/images/tag1.png


TEMPAT SAMPAH
components/BeeBrandPane/images/to.png


+ 67 - 0
components/BeeIcon/BeeIcon.vue

@@ -0,0 +1,67 @@
+<template>
+  <view class="icon-container" @click="$emit('click')">
+    <image
+      :src="src"
+      :style="{
+        width: width ? width : size * 2 + 'rpx',
+        height: width ? height : size * 2 + 'rpx',
+      }"
+      v-if="src"
+      mode=""
+    />
+
+    <tui-icon
+      v-if="name"
+      :name="name"
+      :color="color"
+      :size="size"
+    ></tui-icon>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    src: {
+      type: String,
+      default: '',
+    },
+
+    name: {
+      type: String,
+      default: '',
+    },
+
+    size: {
+      type: Number,
+      default: 20,
+    },
+
+    color: {
+      type: String,
+      default: '#ccc',
+    },
+
+    width: {
+      type: String,
+      default: '',
+    },
+
+    height: {
+      type: String,
+      default: '',
+    },
+  },
+}
+</script>
+
+<style lang="less" scoped>
+.icon-container {
+  flex-shrink: 0;
+  line-height: inherit;
+
+  image {
+    vertical-align: middle;
+  }
+}
+</style>

+ 53 - 0
components/BeeLocale/BeeLocale.vue

@@ -0,0 +1,53 @@
+<template>
+	<view class="bee-locale-container" @click="$jump(`/pages/choose-address/choose-address?eventName=${eventName}`)">
+		<slot>
+			<tui-icon v-if="isIcon" :size="size" name="gps" color="#ffffff"></tui-icon>
+			<BeeIcon v-else :src="require('./locale.png')" :size="size"></BeeIcon>
+			<view class="location-text">
+				<text :style="{ 'fontSize': size * 2 + 'upx' }">{{ $store.getters[field] }}</text>
+			</view>
+		</slot>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'BeeLocale',
+	props: {
+		isIcon: {
+			type: Boolean,
+			default: false
+		},
+		size: {
+			type: Number,
+			default: 16
+		},
+		field: {
+			type: String,
+			default: 'currentCity'
+		},
+		eventName: {
+			type: String,
+			default: ''
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.bee-locale-container {
+  display: flex;
+  align-items: center;
+  margin-right: 10upx;
+
+  .location-text {
+		flex: 1;
+		// width: 0;
+		max-width: 38vw;
+    margin-left: 4upx;
+		white-space: nowrap;
+		overflow: hidden;
+		text-overflow: ellipsis;
+  }
+}
+</style>

TEMPAT SAMPAH
components/BeeLocale/locale.png


+ 103 - 0
components/BeeMenus/BeeMenus.vue

@@ -0,0 +1,103 @@
+<template>
+	<view class="menus-container">
+		<view v-if="data.length > 8">
+			<view v-if="showAll" style="display: flex;align-items: center;justify-content: flex-start;flex-wrap: wrap;">
+				<view
+					v-for="item in data" :key="item.name" class="menu-bar-item" :style="{ width: 100 / column + '%' }"
+					@click="$emit('click', item)"
+				>
+					<BeeIcon
+						:src="item[picUrl] ? common.seamingImgUrl(item[picUrl]) : '../../static/images/index/xiuxian.png'"
+						:size="40"
+					></BeeIcon>
+					<text>{{ item[name] }}</text>
+				</view>
+			</view>
+			<view v-else style="display: flex;align-items: center;justify-content: flex-start;flex-wrap: wrap;">
+				<view
+					v-for="item in data.slice(0, 7)" :key="item.name" class="menu-bar-item" :style="{ width: 100 / column + '%' }"
+					@click="$emit('click', item)"
+				>
+					<BeeIcon
+						:src="item[picUrl] ? common.seamingImgUrl(item[picUrl]) : '../../static/images/index/xiuxian.png'"
+						:size="40"
+					></BeeIcon>
+					<text>{{ item[name] }}</text>
+				</view>
+				<view class="menu-bar-item" :style="{ width: 100 / column + '%' }" @click="handleClickMore">
+					<BeeIcon
+						src="../../static/images/icon/xiangxiajiantou.png"
+						:size="40"
+					></BeeIcon>
+					<text style="color: #888888;">更多</text>
+				</view>
+			</view>
+		</view>
+		<view v-else style="display: flex;align-items: center;justify-content: flex-start;flex-wrap: wrap;">
+			<view
+				v-for="item in data" :key="item.name" class="menu-bar-item" :style="{ width: 100 / column + '%' }"
+				@click="$emit('click', item)"
+			>
+				<BeeIcon
+					:src="item[picUrl] ? common.seamingImgUrl(item[picUrl]) : '../../static/images/index/xiuxian.png'"
+					:size="40"
+				></BeeIcon>
+				<text>{{ item[name] }}</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'BeeMenus',
+	props: {
+		data: {
+			type: Array,
+			required: true
+		},
+
+		column: {
+			type: Number,
+			default: 4
+		},
+
+		name: {
+			type: String,
+			default: 'storeName'
+		},
+
+		picUrl: {
+			type: String,
+			default: 'picUrl'
+		}
+	},
+	data() {
+		return {
+			showAll: false
+		}
+	},
+	methods: {
+		handleClickMore() {
+			this.showAll = true
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.menus-container {
+	background-color: #fff;
+	padding: 30upx 0 0 0;
+	border-radius: 20upx;
+	margin-top: 16upx;
+
+	.menu-bar-item {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+		margin-bottom: 24upx;
+	}
+}
+</style>

+ 194 - 0
components/BeeStoreFilter/BeeStoreFilter.vue

@@ -0,0 +1,194 @@
+<template>
+	<view class="bee-store-filter-container">
+		<view>
+			<tui-dropdown-list :show="dropdownShow" :top="55" background-color="#ffffff" @close="dropdownShow = false">
+				<template #selectionbox>
+					<view class="item" style="height: auto;color: #fa5151;" @click="dropdownShow = !dropdownShow">
+						<view class="title">距离</view>
+						<BeeIcon :src="require('../../static/images/icon/red-arrow.png')" :size="14"></BeeIcon>
+						<text style="font-size: 26upx;">{{ dropdownName || '' }}</text>
+					</view>
+				</template>
+				<template #dropdownbox>
+					<view style="width: fit-content;box-sizing: border-box;">
+						<tui-list-view
+							title="" color="#777" margin-top="2rpx"
+							style="width: fit-content;min-width: 150upx;max-height: 28vh;overflow-y: auto;"
+						>
+							<tui-list-cell
+								padding="20rpx 0"
+								style="width: fit-content;margin: 0 auto;border-bottom: 2upx solid #cccccc;"
+								@click="handleClickDistance(0.5)"
+							>
+								500m内
+							</tui-list-cell>
+							<tui-list-cell
+								padding="20rpx 0"
+								style="width: fit-content;margin: 0 auto;border-bottom: 2upx solid #cccccc;"
+								@click="handleClickDistance(1)"
+							>
+								1km内
+							</tui-list-cell>
+							<tui-list-cell
+								padding="20rpx 0"
+								style="width: fit-content;margin: 0 auto;border-bottom: 2upx solid #cccccc;"
+								@click="handleClickDistance(2)"
+							>
+								2km内
+							</tui-list-cell>
+							<tui-list-cell
+								padding="20rpx 0"
+								style="width: fit-content;margin: 0 auto;border-bottom: 2upx solid #cccccc;"
+								@click="handleClickDistance(3)"
+							>
+								3km内
+							</tui-list-cell>
+							<tui-list-cell
+								padding="20rpx 0"
+								style="width: fit-content;margin: 0 auto;border-bottom: 2upx solid #cccccc;"
+								@click="handleClickDistance(5)"
+							>
+								5km内
+							</tui-list-cell>
+							<tui-list-cell
+								padding="20rpx 0"
+								style="width: fit-content;margin: 0 auto;border-bottom: 2upx solid #cccccc;"
+								@click="handleClickDistance(10)"
+							>
+								10km内
+							</tui-list-cell>
+						</tui-list-view>
+					</view>
+				</template>
+			</tui-dropdown-list>
+		</view>
+
+		<view class="right">
+			<view v-if="isShowFilter">
+				<view class="item" @click="handleClickFilter">
+					<view class="title">筛选</view>
+					<BeeIcon :src="require('./image/filter.png')" :size="14"></BeeIcon>
+					<text style="margin-left: 10upx;font-size: 26upx;">{{ currentTypeName || '' }}</text>
+				</view>
+				<tui-drawer mode="right" :visible="drawerVisible" @close="closeDrawer">
+					<view
+						style="position: relative;width: 350upx;height: calc(100vh - 112upx);padding: 12upx;overflow-y: auto;box-sizing: border-box;"
+					>
+						<view style="margin: 20upx 0;color: #bbbec0;">请选择商家类型:</view>
+						<view style="display: flex;justify-content: space-between;flex-wrap: wrap;">
+							<view style="width: 100%;">
+								<view
+									style="width: fit-content;margin: 10upx 0;padding: 11upx;color: #202124;"
+									:style="{ 'border': currentTypeId === '' ? '1upx solid #4285f4' : '1upx solid #f8eddf', 'backgroundColor': currentTypeId === '' ? '#97e0fd' : '#f8eddf' }"
+									@click="handleClickType({ id: '', storeName: '全部' })"
+								>
+									全部
+								</view>
+							</view>
+							<view
+								v-for="item in storeTypeList" :key="item.id"
+								style="width: fit-content;margin: 10upx 0;padding: 11upx;color: #202124;"
+								:style="{ 'border': currentTypeId === item.id ? '1upx solid #4285f4' : '1upx solid #f8eddf', 'backgroundColor': currentTypeId === item.id ? '#97e0fd' : '#f8eddf' }"
+								@click="handleClickType(item)"
+							>
+								{{ item.storeName }}
+							</view>
+						</view>
+						<view
+							style="position: sticky;bottom: -12upx;display: flex;justify-content: space-between;background-color: #ffffff;"
+						>
+							<tui-button margin="10upx 8upx" height="60rpx" type="warning" shape="circle" @click="closeDrawer">
+								取消
+							</tui-button>
+							<tui-button margin="10upx 8upx" height="60rpx" type="warning" shape="circle" @click="handleClickComfirm">
+								确定
+							</tui-button>
+						</view>
+					</view>
+				</tui-drawer>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { getStoreTypeAllListApi } from '../../api/user'
+export default {
+	name: 'BeeStoreFilter',
+	props: {
+		isShowFilter: {
+			type: Boolean,
+			default: true
+		}
+	},
+	data() {
+		return {
+			dropdownShow: false,
+			dropdownName: false,
+			drawerVisible: false,
+			storeTypeList: [],
+			currentTypeId: '',
+			currentTypeName: ''
+		}
+	},
+	methods: {
+		handleClickFilter() {
+			getStoreTypeAllListApi({})
+				.then((res) => {
+					this.storeTypeList = res.data || []
+				})
+			this.drawerVisible = true
+			this.$emit('click-filter')
+		},
+		closeDrawer() {
+			this.drawerVisible = false
+		},
+		handleClickType(item) {
+			this.currentTypeId = item.id
+			this.currentTypeName = item.storeName
+		},
+		handleClickComfirm() {
+			this.$emit('confirm', { id: this.currentTypeId, storeName: this.currentTypeName })
+			this.drawerVisible = false
+		},
+		handleClickDistance(distance) {
+			this.$emit('select-distance', distance)
+			this.dropdownName = distance + 'km'
+			this.dropdownShow = false
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.bee-store-filter-container {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 14upx 28upx 10upx;
+	margin: 0 28upx;
+	background-color: #fff;
+	border-radius: 20upx 20upx 0 0;
+	box-sizing: border-box;
+
+	/deep/ .tui-dropdown-view {
+		height: auto !important;
+		width: fit-content;
+		margin-left: -28upx;
+	}
+
+	.right {
+		display: flex;
+		align-items: center;
+	}
+
+	.menus {
+		margin-right: 100upx;
+	}
+
+	.item {
+		display: flex;
+		align-items: center;
+	}
+}
+</style>

TEMPAT SAMPAH
components/BeeStoreFilter/image/code.png


TEMPAT SAMPAH
components/BeeStoreFilter/image/filter.png


+ 207 - 0
components/BrandCouponList/BrandCouponList.vue

@@ -0,0 +1,207 @@
+<template>
+	<view class="brand-coupon-container">
+
+		<scroll-view
+			refresher-enabled refresher-background="#3f3d3d" scroll-y style="height: 100%;"
+			scroll-with-animation
+		>
+			<view class="main">
+				<view v-if="couponList && couponList.length">
+					<view v-for="item in couponList" :key="item.id" class="item">
+						<view class="left" :style="{ background: '#FFA74D' }">
+							<view class="money">¥<text>{{ item.discount }}</text></view>
+							<view class="full">满{{ item.min }}可用</view>
+						</view>
+						<view class="right">
+							<view class="type" :style="{ background: '#FFA74D' }">{{ item.name }}</view>
+							<view class="rule">{{ item.desc }}</view>
+							<view v-if="item.days" class="rule">有效天数:{{ item.days }}</view>
+							<view class="list">
+								<view class="time" style="flex: 1;width: 0;">
+									<view v-if="item.startTime && item.endTime">
+										<view style="width: fit-content;">可使用时间范围:</view>
+										<view style="display: flex;flex-wrap: wrap;">
+											<view>{{ new Date(item.startTime).toLocaleString() || '--' }}至</view>
+											<view>{{ new Date(item.endTime).toLocaleString() || '--' }}</view>
+										</view>
+									</view>
+								</view>
+								<view class="order" @click="$emit('click-btn', item)">{{ btnText }}</view>
+							</view>
+						</view>
+					</view>
+				</view>
+				<view v-else>
+					<tui-no-data>暂无优惠券</tui-no-data>
+				</view>
+			</view>
+			<LoadMore v-show="couponList.length" :status="status"></LoadMore>
+		</scroll-view>
+
+	</view>
+</template>
+
+<script>
+import { getBrandCouponListApi } from '../../api/user'
+
+export default {
+	name: 'BrandCouponList',
+	components: {},
+	props: {
+		brandId: {
+			type: [String, Number],
+			default: ''
+		},
+		btnText: {
+			type: String,
+			default: '选择'
+		}
+		// couponList: {
+		// 	type: Array,
+		// 	default() {
+		// 		return []
+		// 	}
+		// }
+	},
+
+	data() {
+		return {
+			couponList: [],
+			status: 'none'
+		}
+	},
+
+	watch: {
+		brandId: {
+			handler(newVal) {
+				if (newVal) {
+					this.getBrandCouponList()
+				}
+			},
+			immediate: true,
+			deep: true
+		}
+	},
+
+	methods: {
+		getBrandCouponList() {
+			if (!this.brandId) return
+			uni.showLoading({
+				title: '加载中'
+			})
+			this.status = 'loading'
+			getBrandCouponListApi({ brandId: this.brandId })
+				.then(({ data }) => {
+					this.couponList = data
+					uni.hideLoading()
+					this.status = 'none'
+				})
+				.catch(() => {
+					uni.hideLoading()
+					this.status = 'none'
+				})
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.brand-coupon-container {
+	height: 100%;
+	padding: 32upx 10upx;
+	box-sizing: border-box;
+	.main {
+		padding: 0upx 4upx 40upx 4upx;
+
+		.item {
+			margin-bottom: 22upx;
+			box-sizing: border-box;
+			width: 100%;
+			// height: 226upx;
+			border-radius: 10upx;
+			box-shadow: 0upx 4upx 10upx 0upx rgba(0, 0, 0, 0.1);
+			background: #FFFFFF;
+			display: flex;
+
+			.left {
+				width: 29%;
+				// padding: 28upx 18upx;
+				display: flex;
+				flex-direction: column;
+				justify-content: center;
+				align-items: center;
+				border-radius: 10upx 0upx 0upx 10upx;
+				color: #FFFFFF;
+
+				.money {
+					font-size: 36upx;
+					font-weight: 500;
+
+					text {
+						font-size: 56upx;
+						font-weight: 500;
+					}
+				}
+
+				.full {
+					font-size: 28upx;
+				}
+			}
+
+			.right {
+				width: 71%;
+				padding: 24upx 20upx 26upx 18upx;
+				box-sizing: border-box;
+
+				.type {
+
+					// width: 110upx;
+					width: fit-content;
+					height: 40upx;
+					padding: 0 20upx;
+					border-radius: 100upx;
+					background: #89AEFC;
+					color: #FFFFFF;
+					font-size: 24upx;
+					font-weight: 500;
+					display: flex;
+					justify-content: center;
+					align-items: center;
+				}
+
+				.rule {
+					padding-top: 4upx;
+					padding-bottom: 2upx;
+					font-size: 28upx;
+					color: #3D3D3D;
+				}
+
+				.list {
+					padding-top: 20upx;
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+
+					.time {
+						font-size: 20upx;
+						color: #777777;
+					}
+
+					.order {
+						width: 148upx;
+						height: 40upx;
+						border-radius: 100upx;
+						background: #FF4654;
+						font-size: 24upx;
+						font-weight: 500;
+						color: #FFFFFF;
+						display: flex;
+						justify-content: center;
+						align-items: center;
+					}
+				}
+			}
+		}
+	}
+}
+</style>

+ 144 - 0
components/BrandGoods/BrandGoods.vue

@@ -0,0 +1,144 @@
+<template>
+	<view v-if="goodsData" class="brand-goods-container">
+		<view style="max-height: 440upx;min-height: 120upx;overflow: hidden;">
+			<tui-lazyload-img
+				class="goods-img" mode="widthFix"
+				width="340rpx" height="auto" :src="common.seamingImgUrl(goodsData.picUrl)" @click="$emit('click-content', { ...goodsData, ruleId })"
+			></tui-lazyload-img>
+		</view>
+
+		<view class=" brand-goods-name">
+			{{ goodsData.name }}
+		</view>
+
+		<view class="time">
+			<view class="wrapper" @click="$emit('click-content', { ...goodsData, ruleId })">
+				<view class="price-wrapper">
+					<text class="price-text">
+						¥
+						<text v-if="ruleId">{{ goodsData.counterPrice - grouponPrice }}</text>
+						<text v-else-if="seckillGoodId">{{ seckillPrice }}</text>
+						<text v-else>{{ goodsData.counterPrice }}</text>
+						{{ ruleId ? goodsData.counterPrice + grouponPrice : goodsData.counterPrice }}
+					</text>
+					<text v-if="goodsData.isHot" class="price-tag">热卖</text>
+				</view>
+			</view>
+
+			<view v-if="ruleId">
+				<tui-button
+					type="danger" width="168rpx" height="60rpx" margin="0 10rpx 0 0"
+					style="border-radius: 50rpx;" @click="$emit('add', { ...goodsData, ruleId })"
+				>
+					发起团购
+				</tui-button>
+			</view>
+
+			<view v-else-if="seckillGoodId">
+				<tui-button
+					type="danger" width="168rpx" height="60rpx" margin="0 10rpx 0 0"
+					style="border-radius: 50rpx;" @click="$emit('add', { ...goodsData, seckillGoodId })"
+				>
+					秒杀
+				</tui-button>
+			</view>
+			<view v-else>
+				<BeeIcon
+					:size="22" :src="require('../../static/images/icon/add-icon.png')"
+					@click="$emit('add', { ...goodsData, ruleId })"
+				></BeeIcon>
+			</view>
+		</view>
+
+		<view style="display: flex;justify-content: space-between;padding: 0 10upx;;font-size: 26upx;color: #777777;">
+			<text>{{ goodsData.browse }}浏览量</text>
+			<text>销量 {{ goodsData.sales }}</text>
+		</view>
+	</view>
+</template>
+
+<script>
+// import { getRandom } from '../../utils'
+export default {
+	props: {
+		goodsData: {
+			type: Object,
+			required: true
+		},
+
+		ruleId: {
+			type: [Number, String],
+			default: ''
+		},
+		grouponPrice: {
+			type: Number,
+			default: 0
+		},
+		grouponMember: {
+			type: Number,
+			default: 0
+		},
+
+		seckillGoodId: {
+			type: Number,
+			default: ''
+		},
+		seckillPrice: {
+			type: Number,
+			default: 0
+		}
+	},
+
+	methods: {
+		// getRandom
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.brand-goods-container {
+	width: 340upx;
+	margin-bottom: 40upx;
+
+	.brand-goods-name {
+		color: #3d3d3d;
+		font-weight: 500;
+		width: 340upx;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+	}
+
+	.time {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		margin: 10upx 0;
+
+		.wrapper {
+			.price-text {
+				color: #fa5151;
+				font-weight: bold;
+				font-size: 28upx;
+			}
+
+			.price-tag {
+				padding: 0 10upx;
+				border: 1upx solid #fa5151;
+				font-size: 20upx;
+				color: #fa5151;
+				border-radius: 8upx;
+				margin-left: 9upx;
+			}
+		}
+
+		.bee-btn {
+			width: 102upx;
+			height: 44upx;
+			background-color: #fa5151;
+			border-radius: 10upx;
+			color: #fff;
+		}
+	}
+}
+</style>

+ 237 - 0
components/BrandGoodsList/BrandGoodsList.vue

@@ -0,0 +1,237 @@
+<template>
+	<view class="shop-car-container">
+
+		<scroll-view
+			refresher-enabled refresher-background="#3f3d3d" scroll-y style="height: 100%;"
+			scroll-with-animation
+		>
+			<!-- <view class="shop-list">
+				<view class="shop-car-list">
+
+				<view class="shop-goods-list">
+				<view v-if="brandDetail.goodsVoList && brandDetail.goodsVoList.length">
+				<view v-for="(item, index) in brandDetail.goodsVoList" :key="item.id" class="goods-item">
+				<JAvatar radius="10" :size="120" :src="common.seamingImgUrl(item.picUrl)"></JAvatar>
+
+				<view class="goods-pane-right">
+				<view class="goods-pane-name">{{ item.name.trim() }} </view>
+				<view class="goods-pane-desc-content">
+				<text class="goods-pane-desc">
+				商品编号:{{ item.goodsSn }}
+				</text>
+				</view>
+				<view class="goods-pane-footer">
+				<text class="goods-pane-price">¥{{ item.retailPrice }}</text>
+				<view class="ops">
+				<tui-button type="warning" width="120rpx" height="50rpx" shape="circle" @click="handleChooseGoods(item)">选择</tui-button>
+				</view>
+				</view>
+				</view>
+				</view>
+				</view>
+				<view v-else>
+				<tui-no-data>暂无数据</tui-no-data>
+				</view>
+				</view>
+				</view>
+				</view> -->
+			<view class="store-goods-list-container">
+				<view
+					v-if="brandDetail.categoryList && brandDetail.categoryList.length"
+					style="display: flex;box-sizing: border-box;"
+				>
+					<view style="background-color: #f3f3f3;">
+						<view
+							v-for="item in brandDetail.categoryList" :key="item.serverNameOne"
+							style="max-width: 144upx;padding: 20upx 28upx;word-break: break-all;box-sizing: border-box;"
+							:style="{ backgroundColor: item.id === currentTab ? '#ffffff' : 'transparent' }"
+							@click="(currentTab = item.id) && (currentGoods = brandDetail.categoryList.find(part => part.id === item.id).goodsList || [])"
+						>
+							{{ item.name }}
+						</view>
+					</view>
+					<view style="flex: 1;padding: 20upx;">
+						<view v-if="currentGoods && currentGoods.length">
+							<view v-for="item in currentGoods" :key="item.id">
+								<StoreGoods :goods-data="item" :show-icon="false" :show-tag="false" :show-msg="false" show-sn>
+									<template #button>
+										<tui-button
+											v-if="showSelectBtn" type="warning" width="120rpx" height="50rpx"
+											shape="circle"
+											@click="handleChooseGoods(item)"
+										>
+											选择
+										</tui-button>
+										<slot name="button" :data="item"></slot>
+									</template>
+								</StoreGoods>
+							</view>
+						</view>
+						<view v-else style="margin: 40upx 0;text-align: center;">
+							暂无商品~
+						</view>
+					</view>
+				</view>
+				<view v-else class="no-data">
+					暂无数据
+				</view>
+			</view>
+		</scroll-view>
+
+	</view>
+</template>
+
+<script>
+// import {
+// 	getBrandDetailApi
+// } from '../../api/brand'
+
+export default {
+	name: 'BrandGoodsList',
+	components: {},
+	props: {
+		showSelectBtn: {
+			type: Boolean,
+			default: true
+		},
+		brandId: {
+			type: [String, Number],
+			required: true
+		}
+	},
+
+	data() {
+		return {
+			currentTab: '',
+			currentGoods: [],
+			brandDetail: {}
+		}
+	},
+
+	computed: {},
+
+	watch: {
+		brandDetail: {
+			handler(newVal) {
+				if (newVal) {
+					if (this.brandDetail.categoryList && this.brandDetail.categoryList.length) {
+						(this.currentTab = this.brandDetail.categoryList[0].id) && (this.currentGoods = this.brandDetail.categoryList[0].goodsList || [])
+					}
+				}
+			},
+			immediate: true,
+			deep: true
+		}
+	},
+
+	created() {
+		this.getGoodsList()
+	},
+
+	methods: {
+		// 获取商品列表
+		async getGoodsList() {
+			if (!this.brandId) return
+			uni.showLoading({
+				title: '加载中'
+			})
+			const { data } = await getBrandDetailApi({
+				id: this.brandId,
+				longitude: this.$store.state.location.locationInfo.streetNumber.location.split(',')[0],
+				latitude: this.$store.state.location.locationInfo.streetNumber.location.split(',')[1]
+			})
+			this.brandDetail = data || {}
+			uni.hideLoading()
+		},
+
+		// 选择
+		handleChooseGoods(goods) {
+			// this.$emit('send', { goodsId: goods.id, goodsName: goods.name || '' })
+			this.$emit('send', goods)
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+
+.shop-car-container {
+	height: 100%;
+	padding: 32upx 10upx;
+	box-sizing: border-box;
+
+	// .shop-list {
+	// 	// height: 100%;
+	// 	// overflow-y: auto;
+
+	// 	.shop-car-list {
+	// 		margin-bottom: 20px;
+	// 	}
+
+	// 	.goods-item {
+	// 		.flex();
+	// 		box-sizing: border-box;
+	// 		margin-top: 30upx;
+
+	// 		.goods-pane-right {
+	// 			width: 100%;
+	// 			margin-left: 30upx;
+	// 			font-size: @f12;
+	// 			color: @c0;
+
+	// 			.goods-pane-name {
+	// 				width: 404upx;
+	// 				white-space: nowrap;
+	// 				text-overflow: ellipsis;
+	// 				overflow: hidden;
+	// 			}
+
+	// 			.goods-pane-desc-content {
+	// 				margin: 14upx 0 10upx 0;
+	// 			}
+
+	// 			.goods-pane-desc {
+	// 				font-size: 20upx;
+	// 				margin-right: 10upx;
+	// 			}
+
+	// 			.goods-pane-footer {
+	// 				.flex();
+
+	// 				.goods-pane-price {
+	// 					color: #fa5151;
+	// 					font-size: 28upx;
+	// 				}
+
+	// 				.ops {
+	// 					margin-right: 40rpx;
+	// 					background-color: #f5f5f5;
+	// 				}
+	// 			}
+	// 		}
+	// 	}
+
+	// 	.no-data {
+	// 		.flex(center, center);
+	// 		min-height: 180upx;
+	// 		color: #999999;
+	// 		font-size: 36upx;
+	// 		letter-spacing: 2px;
+	// 	}
+	// }
+	.store-goods-list-container {
+		width: 100%;
+		font-size: 32upx;
+
+		.no-data {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			min-height: 200upx;
+			color: #ccc;
+			padding: 20upx 0;
+			flex-direction: column;
+		}
+	}
+}
+</style>

+ 312 - 0
components/CashierList/CashierList.vue

@@ -0,0 +1,312 @@
+<template>
+	<view class="cashier-list-content">
+		<view v-if="show">
+			<u-radio-group v-model="paymentMode" placement="row" icon-placement="right" @change="handleChangePaymentMode">
+				<view v-for="payment in paymentList" :key="payment.id" class="cashier">
+					<view class="cashier-item" @click="handleChangePaymentMode(payment.paymentMode, payment.disabled)">
+						<view class="icon-text">
+							<image class="pay-type-img-inner" :src="payment.icon" mode="widthFix" />
+							{{ payment.label }}
+							<span v-if="paymentMode === 3 && paymentMode === payment.paymentMode">
+								(手续费:¥{{ flowerObj.hbServiceChargeTotal }})
+							</span>
+						</view>
+						<view class="radio">
+							<u-radio :disabled="payment.disabled" active-color="#c5aa7b" :name="payment.paymentMode" />
+						</view>
+					</view>
+					<!-- 花呗分期 -->
+					<view v-if="paymentMode === 3 && paymentMode === payment.paymentMode" class="ali-hb-content">
+						<u-radio-group
+							v-model="flowerObj.hbByStagesPeriods" placement="row" icon-placement="right"
+							@change="handleChangePeriods"
+						>
+							<view v-for="(flowerItem, index) in flowerObj.hbByStagesList" :key="index" class="cashier">
+								<view class="cashier-item" @click="handleChangePeriods(flowerItem.numberOfStages, flowerItem.disabled)">
+									<view class="icon-text">
+										{{ flowerItem.numberOfStages }}期(¥{{ flowerItem.price }}/期)
+									</view>
+									<view class="radio-context">
+										手续费:¥{{ flowerItem.serviceCharge }}/期
+										<u-radio
+											class="radio" active-color="#c5aa7b" :disabled="flowerItem.disabled"
+											:name="flowerItem.numberOfStages"
+										/>
+									</view>
+								</view>
+							</view>
+						</u-radio-group>
+					</view>
+				</view>
+			</u-radio-group>
+		</view>
+	</view>
+</template>
+
+<script>
+import { getOrderHuabeiConfigApi } from '../../api/user'
+
+export default {
+	name: 'CashierList',
+	props: {
+		totalPrice: {
+			type: Number,
+			default: () => 200
+		},
+		// 是否显示,用于默认某一个支付
+		show: {
+			type: Boolean,
+			default: false
+		}
+	},
+	data() {
+		return {
+			paymentMode: 1, // 支付方式 1微信 2支付宝 3花呗分期
+			paymentList: [
+				// {
+				// 	id: 1,
+				// 	label: '微信支付',
+				// 	paymentMode: 1,
+				// 	icon: require('../../static/images/user/pay/wechat_pay.png'),
+				// 	disabled: false
+				// },
+				// {
+				// 	id: 2,
+				// 	label: '支付宝支付',
+				// 	paymentMode: 2,
+				// 	icon: require('../../static/images/user/pay/alipay.png'),
+				// 	disabled: false
+				// },
+				// {
+				// 	id: 3,
+				// 	label: '花呗分期',
+				// 	paymentMode: 3,
+				// 	icon: require('../../static/images/user/pay/huabei.png'),
+				// 	disabled: false
+				// },
+				{
+					id: 4,
+					label: '通联支付',
+					paymentMode: 4,
+					icon: require('../../static/images/user/pay/tonglian.png'),
+					disabled: false
+				}
+			],
+			// 花呗相关
+			flowerObj: {
+				hbChargeType: 1, // 花呗手续费支付方式 1-商户支付 2-用户支付 后端接口返回
+				hbByStagesPeriods: -1, // 花呗分期期数 3 6 12
+				hbByStagesList: [
+					{
+						rate: 0,
+						price: 0,
+						numberOfStages: 3,
+						serviceCharge: 0,
+						disabled: false
+					},
+					{
+						rate: 0,
+						price: 0,
+						numberOfStages: 6,
+						serviceCharge: 0,
+						disabled: false
+					},
+					{
+						rate: 0,
+						price: 0,
+						numberOfStages: 12,
+						serviceCharge: 0,
+						disabled: false
+					}
+				], // 花呗手续费比例列表 【{3期},{6期},{12期}】
+				hbServiceChargeTotal: 0 // 花呗支付总手续费
+			}
+		}
+	},
+	mounted() {
+		this.getTheFlowerConfig()
+		this.handleSetDisable()
+		this.handleNoticeFather()
+	},
+	methods: {
+		/**
+		 * 根据环境更改可选支付项
+		 */
+
+		handleSetDisable() {
+			// #ifdef MP-WEIXIN
+			// this.paymentList[0].disabled = false
+			// this.paymentList[1].disabled = true
+			// this.paymentList[2].disabled = true
+			this.paymentList[0].disabled = false
+			this.paymentMode = 4 // 1
+			// #endif
+			// #ifdef MP-ALIPAY
+			// this.paymentList[0].disabled = true
+			// this.paymentList[1].disabled = false
+			// this.paymentList[2].disabled = false
+			this.paymentList[0].disabled = false
+			this.paymentMode = 4 // 2
+			// #endif
+			// #ifdef APP || H5
+			// this.paymentList[0].disabled = false
+			// this.paymentList[1].disabled = true
+			// this.paymentList[2].disabled = true
+			this.paymentList[0].disabled = false
+			this.paymentMode = 4 // 1
+			// #endif
+		},
+
+		/**
+		 * 支付方式改变事件
+		 * @param paymentMode
+		 * @param disabled
+		 */
+
+		handleChangePaymentMode(paymentMode, disabled = false) {
+			if (disabled) return
+			this.paymentMode = paymentMode
+			const { flowerObj } = this
+			if ([1, 2].includes(paymentMode)) {
+				// 支付宝支付,取消分期选择
+				flowerObj.hbByStagesPeriods = -1
+				// 3 6 12 全部禁止
+				flowerObj.hbByStagesList.map((item) => {
+					item.disabled = true
+				})
+			} else {
+				// 分期支付,默认选三期
+				flowerObj.hbByStagesPeriods = 3
+			}
+			this.handleHbStagesAndPrice()
+			this.handleNoticeFather()
+		},
+
+		/**
+		 * 获取花呗分期配置
+		 */
+
+		async getTheFlowerConfig() {
+			const { data } = await getOrderHuabeiConfigApi({})
+			const { flowerObj } = this
+			flowerObj.hbChargeType = data.huabeiChargeType
+			// 如果后端返回的是用户支付手续费,设置费率信息
+			if (data.huabeiChargeType === 1) {
+				data.huabeiFeeRateList.forEach((rate, index) => {
+					flowerObj.hbByStagesList[index].rate = rate
+				})
+			}
+		},
+
+		/**
+		 * 处理花呗期数选择
+		 * @param periods 期数
+		 * @param disabled
+		 */
+
+		handleChangePeriods(periods, disable = false) {
+			if (disable) return
+			const { flowerObj } = this
+			flowerObj.hbByStagesPeriods = periods
+			this.handleHbStagesAndPrice()
+			this.handleNoticeFather()
+		},
+
+		/**
+		 * 处理花呗价格和手续费显示
+		 */
+
+		handleHbStagesAndPrice() {
+			// const { flowerObj, totalPrice } = this
+			const flowerObj = this.flowerObj
+			const totalPrice = this.totalPrice || 0
+			if (this.paymentMode !== 3) return
+			flowerObj.hbByStagesList.forEach((stages) => {
+				// 根据价格填充每一期价格和手续费信息
+				stages.price = ((totalPrice * (1 + stages.rate / 100)) / stages.numberOfStages).toFixed(2) // 每一期价格
+				stages.serviceCharge = ((totalPrice * (stages.rate / 100)) / stages.numberOfStages).toFixed(2) // 每一期手续费
+				// 计算总手续费
+				if (stages.numberOfStages === Number(flowerObj.hbByStagesPeriods)) {
+					flowerObj.hbServiceChargeTotal = (totalPrice * (stages.rate / 100)).toFixed(2)
+				}
+				// 处理允许分期的区间,公式为总价格要大于分期数/100
+				this.totalPrice < stages.numberOfStages / 100 ? stages.disabled = true : stages.disabled = false
+			})
+		},
+
+		/**
+		 * 通知父组件
+		 */
+
+		handleNoticeFather() {
+			const { paymentMode, flowerObj } = this
+			const params = {
+				paymentMode,
+				huabeiPeriod: flowerObj.hbByStagesPeriods
+			}
+			this.$emit('change', params)
+		}
+	}
+}
+</script>
+
+<style
+    lang="scss"
+    scoped
+>
+.cashier-list-content {
+	width: 100%;
+	padding: 0rpx 15rpx;
+	box-sizing: border-box;
+	background: #fff;
+
+	.u-radio-group {
+		display: block !important;
+	}
+
+	.cashier {
+		border-bottom: 2rpx solid #d0d0d0;
+
+		&:last-child {
+			border-bottom: none
+		}
+
+		.cashier-item {
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+			padding: 20rpx 0;
+			box-sizing: border-box;
+			position: relative;
+
+			.icon-text {
+				display: flex;
+				align-items: center;
+				justify-content: center;
+
+				image {
+					width: 50rpx;
+					height: 50rpx;
+					margin-right: 15rpx;
+				}
+			}
+
+			.radio-context {
+				display: flex;
+				align-items: center;
+				font-size: 14rpx;
+
+				.radio {
+					margin-left: 15rpx;
+				}
+			}
+		}
+
+		.ali-hb-content {
+			padding: 10rpx 20px;
+			box-sizing: border-box;
+			border-top: 2rpx solid #d0d0d0;
+		}
+	}
+}
+</style>

+ 23 - 0
components/CategoryIcon/CategoryIcon.vue

@@ -0,0 +1,23 @@
+<template>
+	<view style="display: flex;flex-direction: column;align-items: center;" @click="$jump('/pages/category/index')">
+		<tui-icon class="search-icon" name="manage" color="#000000" :size="18"></tui-icon>
+		<text style="font-size: 26upx;">分类</text>
+	</view>
+</template>
+
+<script>
+export default {
+	methods: {
+		handleClick() {
+			uni.showToast({
+				title: '功能未开放',
+				duration: 2000,
+				icon: 'none'
+			})
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+</style>

+ 79 - 0
components/DefaultHead/index.vue

@@ -0,0 +1,79 @@
+<template>
+	<view class="default_head_content">
+		<!--  小程序不支持style写computed形式  -->
+		<view
+			v-if="needSetStatusBarHeight" class="status_bar_fit" :style="{
+				'height': `${statusBarHeight}px`,
+				'background-color': `${backgroundColor}`
+			}"
+		></view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'DefaultHead',
+	props: {
+		needSetStatusBarHeight: {
+			type: Boolean,
+			default: () => true
+		},
+		backgroundColor: {
+			type: String,
+			default: () => '#fff'
+		}
+	},
+	emits: [ 'getInfoData' ],
+	data() {
+		return {
+			// 系统信息
+			systemInfo: {},
+			// 小程序胶囊信息
+			menuButtonInfo: {
+				width: 0,
+				height: 0,
+				top: 0,
+				left: 0,
+				bottom: 0,
+				right: 0
+			},
+			// 手机状态栏高度
+			statusBarHeight: 0
+		}
+	},
+	mounted() {
+		this.handleGetSystemInfo()
+		// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
+		this.handleGetMenuButtonInfo()
+		// #endif
+		this.$emit('getInfoData', {
+			systemInfo: this.systemInfo,
+			menuButtonInfo: this.menuButtonInfo
+		})
+	},
+	methods: {
+		handleGetSystemInfo() {
+			this.systemInfo = uni.getSystemInfoSync()
+			this.statusBarHeight = this.systemInfo.statusBarHeight
+		},
+
+		handleGetMenuButtonInfo() {
+			this.menuButtonInfo = uni.getMenuButtonBoundingClientRect()
+		}
+	}
+}
+</script>
+
+<style
+    lang="scss"
+    scoped
+>
+.default_head_content {
+	width: 100%;
+	background: #fff;
+
+	.status_bar_fit {
+		width: 100%;
+	}
+}
+</style>

+ 153 - 0
components/DragButton/DragButton.vue

@@ -0,0 +1,153 @@
+<template>
+	<view
+		id="_drag_button" class="drag" :style="'left: ' + left + 'px; top:' + top + 'px;'"
+		:class="{ transition: isDock && !isMove }" @touchstart="touchstart" @touchmove.stop.prevent="touchmove"
+		@touchend="touchend" @click.stop.prevent="click"
+	>
+		<view class="drag-icon">
+			<BeeIcon :size="42" :src="iconSrc"></BeeIcon>
+		</view>
+		<view class="drag-text">{{ text }}</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'DragButton',
+	props: {
+		text: {
+			type: String,
+			default: '按钮'
+		},
+		iconSrc: {
+			type: String,
+			required: true
+		},
+		isDock: {
+			type: Boolean,
+			default: false
+		},
+		existTabBar: {
+			type: Boolean,
+			default: false
+		}
+	},
+	data() {
+		return {
+			top: 0,
+			left: 0,
+			width: 0,
+			height: 0,
+			offsetWidth: 0,
+			offsetHeight: 0,
+			windowWidth: 0,
+			windowHeight: 0,
+			isMove: true,
+			edge: 10
+		}
+	},
+	mounted() {
+		const sys = uni.getSystemInfoSync()
+		this.windowWidth = sys.windowWidth
+		this.windowHeight = sys.windowHeight
+		// #ifdef APP-PLUS
+		this.existTabBar && (this.windowHeight -= 50)
+		// #endif
+		if (sys.windowTop) {
+			this.windowHeight += sys.windowTop
+		}
+		// console.log(sys)
+		const query = uni.createSelectorQuery().in(this)
+		query.select('#_drag_button').boundingClientRect((data) => {
+			this.width = data.width
+			this.height = data.height
+			this.offsetWidth = data.width / 2
+			this.offsetHeight = data.height / 2 + 60
+			this.left = this.windowWidth - this.width - this.edge
+			this.top = this.windowHeight - this.height - this.edge - 40
+		})
+			.exec()
+	},
+	methods: {
+		click() {
+			this.$emit('btnClick')
+		},
+		touchstart(e) {
+			this.$emit('btnTouchstart')
+		},
+		touchmove(e) {
+			// 单指触摸
+			if (e.touches.length !== 1) return false
+			this.isMove = true
+			this.left = e.touches[0].clientX - this.offsetWidth
+			let clientY = e.touches[0].clientY - this.offsetHeight
+			// #ifdef H5
+			clientY += this.height
+			// #endif
+			const edgeBottom = this.windowHeight - this.height - this.edge
+			// 上下触及边界
+			if (clientY < this.edge) {
+				// console.log(11111)
+				this.top = this.edge
+			} else if (clientY > edgeBottom) {
+				// console.log(22222)
+				this.top = edgeBottom
+			} else {
+				// console.log(33333)
+				this.top = clientY
+			}
+		},
+		touchend(e) {
+			if (this.isDock) {
+				const edgeRigth = this.windowWidth - this.width - this.edge
+				if (this.left < this.windowWidth / 2 - this.offsetWidth) {
+					this.left = this.edge
+				} else {
+					this.left = edgeRigth
+				}
+			}
+			this.isMove = false
+			this.$emit('btnTouchend')
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+$uni-font-size-sm: 24upx;
+$uni-text-color-inverse: #979797;
+
+.drag {
+	position: fixed;
+	z-index: 999999;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+	padding: 6upx 20upx;
+	background-color: transparent;
+	// border: 1upx solid #a6f8d6;
+	border-radius: 60upx;
+	font-size: $uni-font-size-sm;
+	color: $uni-text-color-inverse;
+	box-sizing: border-box;
+
+	.drag-icon {
+		padding: 6upx;
+		background-color: #ffffff;
+		// box-shadow: 0 0 6upx rgba(0, 0, 0, 0.4);
+		border-radius: 50%;
+	}
+
+	.drag-text {
+		padding: 8upx;
+		background-color: #ffffff;
+		border-radius: 28upx;
+		box-shadow: 0px 4px 10px 0px rgba(4, 10, 19, 0.2);
+	}
+
+	&.transition {
+		transition: left .3s ease, top .3s ease;
+	}
+}
+</style>

+ 68 - 0
components/Empty/index.vue

@@ -0,0 +1,68 @@
+<template>
+	<view
+		v-if="show" class="empty-content" :style="{
+			paddingTop: paddingTop + 'rpx',
+			background
+		}"
+	>
+		<image :src="iconUrl" />
+		<p class="tips">
+			<slot>
+				暂无数据~
+			</slot>
+		</p>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'Empty',
+	props: {
+		show: {
+			type: Boolean,
+			default: false
+		},
+		paddingTop: {
+			type: Number,
+			default: () => 500
+		},
+		background: {
+			type: String,
+			default: () => '#f8f8f8'
+		},
+		iconUrl: {
+			type: String,
+			default: () => require('../../static/images/origin/searchEmpty.png')
+		}
+	},
+	data() {
+		return {}
+	},
+	computed: {
+
+	},
+	methods: {}
+}
+</script>
+
+<style
+    lang="scss"
+    scoped
+>
+.empty-content {
+	display: flex;
+	justify-content: center;
+	flex-direction: column;
+	align-items: center;
+
+	image {
+		width: 150rpx;
+		height: 150rpx;
+	}
+
+	.tips {
+		margin-top: 25rpx;
+		font-weight: bolder;
+	}
+}
+</style>

+ 46 - 0
components/JAvatar/JAvatar.vue

@@ -0,0 +1,46 @@
+<template>
+	<view class="j-avatar">
+		<image
+			:style="{
+				'width': size * 1 + 'upx',
+				'height': size * 1 + 'upx',
+				'border-radius': radius.includes('%') ? radius : radius * 1 + 'upx',
+				border
+			}"
+			class="img"
+			:src="src"
+		/>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		src: {
+			type: String,
+			required: true
+		},
+
+		size: {
+			type: [Number, String],
+			default: 60
+		},
+
+		radius: {
+			type: [Number, String],
+			default: '50%'
+		},
+
+		border: {
+			type: String,
+			default: ''
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.img {
+  object-fit: cover;
+}
+</style>

+ 42 - 0
components/JBack/JBack.vue

@@ -0,0 +1,42 @@
+<template>
+	<view class="j-back-container">
+		<image
+			:style="{
+				width: width * 1 + 'upx',
+				height: height * 1 + 'upx'
+			}" :src="dark
+				? require('../../static/images/icon/darkBack.png')
+				: require('../../static/images/icon/lightBack.png')
+			" mode="" @click="handleBack"
+		/>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'JBack',
+	props: {
+		tabbar: {
+			type: String
+		},
+		width: {
+			type: [Number, String],
+			default: 16
+		},
+		height: {
+			type: [Number, String],
+			default: 28
+		},
+		dark: Boolean
+	},
+	methods: {
+		handleBack() {
+			if (this.tabbar) {
+				this.$switchTab(this.tabbar)
+				return
+			}
+			uni.navigateBack()
+		}
+	}
+}
+</script>

+ 54 - 0
components/JHeader/JHeader.vue

@@ -0,0 +1,54 @@
+<template>
+	<view class="j-header-container">
+		<JBack v-bind="$attrs" :dark="dark"></JBack>
+		<view
+			class="title" :style="{
+				color: !dark ? '#fff' : ''
+			}"
+		>
+			{{ title }}
+		</view>
+		<view class="footerFn">
+			<slot name="ftFn"></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'JHeader',
+	props: {
+		dark: {
+			type: Boolean,
+			default: true
+		},
+		title: {
+			type: String,
+			required: true
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.j-header-container {
+	position: relative;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+
+	.title {
+		font-size: 32upx;
+		color: #3d3d3d;
+		font-weight: bold;
+		// margin-top: -8upx;
+		flex: 1;
+		text-align: center;
+		margin-left: -50upx;
+	}
+	.footerFn {
+		position: absolute;
+		right: 0rpx;
+	}
+}
+</style>

+ 113 - 0
components/JRedEnvelope/JRedEnvelope.vue

@@ -0,0 +1,113 @@
+<template>
+	<view class="j-red-envelope">
+		<tui-landscape
+			:show="isShow" :position="1" :close-icon="showType === 0 ? false : true"
+			:mask-closable="showType === 0 ? true : false" icon-left="50rpx" icon-right="50rpx" @close="$emit('close')"
+		>
+			<view>
+				<view v-if="showType === 0">
+					<view @click="$emit('click-red')">
+						<image
+							mode="heightFix" src="../../static/images/index/red-wrap.png"
+							style="min-height: 45vh;max-height: 5vh;"
+						/>
+					</view>
+				</view>
+				<view v-else-if="showType === 1">
+					<view
+						style="padding-bottom: 22upx;background: linear-gradient(180deg, #70391C 93%, #8E2216 99%);border-radius: 48upx;"
+					>
+						<view class="j-red-envelope-container">
+							<view style="display: flex;align-items: center;">
+								<JAvatar
+									:size="82"
+									:src="data.brandAvatar || data.userAvatar || '../../static/images/icon/avatar.png'"
+								>
+								</JAvatar>
+								<view style="flex: 1;margin-left: 28upx;color: #FFFFFF;">
+									<view style="font-size: 38upx;font-weight: bold;">
+										{{ data.brandName || data.username || '--' }}的红包
+									</view>
+									<view
+										style="width: 100%;word-break: break-all;display: -webkit-box;overflow: hidden;-webkit-box-orient: vertical;-webkit-line-clamp: 2;"
+									>
+										{{ data.wrapRedText.publisherText || "恭喜发财,财源滚滚" }}
+									</view>
+								</view>
+							</view>
+							<view v-if="data.wrapRedText.bindLink" style="margin-top: 20upx;text-align: center;">
+								<tui-button
+									type="white" plain width="340rpx" height="60rpx"
+									shape="circle" style="display: inline-block;"
+									@click="$jump(`/goodsModule/goodsDetails?shopId=${data.wrapRedText.bindLink.shopId}&productId=${data.wrapRedText.bindLink.productId}&skuId=${data.wrapRedText.bindLink.skuId}`)"
+								>
+									导航到商品→
+								</tui-button>
+							</view>
+							<view style="margin-top: 34upx;margin-bottom: 38upx;text-align: center;">
+								<view style="font-size: 34upx;color: #FFCC66;">
+									恭喜你获得{{ data.wrapRedText.business ? '携带优惠券' : '现金' }}红包
+								</view>
+								<view style="margin-top: 20upx;color: #FFE019;">
+									<text style="font-size: 54upx;font-weight: bold;">{{ data.wrapRedText.redpackMonkey || '--' }}</text>
+									<text style="margin-left: 6upx;font-size: 34upx;">元</text>
+								</view>
+								<view style="margin-top: 28upx;font-size: 26upx;color: #ffffff;">
+									<view>红包已自动存入余额</view>
+									<view v-if="data.wrapRedText.business">商家优惠券已自动放入券包</view>
+								</view>
+							</view>
+							<view style="max-height: 38vh;overflow-y: auto;">
+								<view style="border: 2upx solid #FFFFFF;border-radius: 28upx;overflow: hidden;">
+									<image
+										mode="widthFix" :src="data.wrapRedText.picUrl || '../../static/images/index/red-pic.png'"
+										style="min-width: 450upx;max-width: 550upx;max-height: 50vh;vertical-align: middle;"
+									/>
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</tui-landscape>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'JRedEnvelope',
+	props: {
+		isShow: Boolean,
+		showType: Number,
+		data: {
+			type: Object,
+			default() {
+				return {
+					wrapRedText: {}
+				}
+			}
+		}
+	},
+
+	data() {
+		return {
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.j-red-envelope {
+	.j-red-envelope-container {
+		width: 100%;
+		padding: 48upx 40upx 36upx 40upx;
+		background: linear-gradient(180deg, #F58F5A 0%, #F53F2A 100%);
+		border-radius: 38upx 38upx 48upx 48upx;
+		box-sizing: border-box;
+	}
+
+	/deep/ .tui-landscape__inner {
+		top: 45%;
+	}
+}
+</style>

+ 155 - 0
components/ListBottomTips/index.vue

@@ -0,0 +1,155 @@
+<template>
+	<view class="bottom_tips_content">
+		<view class="line_inner_row" :class="[ loading ? 'is_loading' : '' ]">
+			<view class="text_box">
+				<view
+					v-for="(item, index) in type === 0 ? onBottomText : loadingText" :key="index" class="text_item"
+					:class="[loading ? 'text_loading' : '', `delay-${index % 10}`]"
+				>
+					{{ item }}
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'ListBottomTips',
+	props: {
+		loadingText: {
+			type: String,
+			default: '加载中...'
+		},
+		onBottomText: {
+			type: String,
+			default: '已经到达底部了~'
+		},
+		type: {
+			type: Number,
+			default: 0 // 0底部1加载
+		},
+		loading: {
+			type: Boolean,
+			default: true
+		}
+	},
+	data() {
+		return {}
+	},
+	methods: {}
+}
+</script>
+
+<style
+    lang="scss"
+    scoped
+>
+.bottom_tips_content {
+	padding: 20rpx 30rpx;
+	box-sizing: border-box;
+
+	.line_inner_row {
+		width: 100%;
+		position: relative;
+		display: flex;
+		align-items: center;
+		justify-content: space-around;
+		font-weight: bold;
+
+		.text_box {
+			display: flex;
+
+			.text_item {
+				animation-delay: calc(.1s * var(--jumpTime))
+			}
+
+			.delay-0 {
+				animation-delay: calc(0s)
+			}
+
+			.delay-1 {
+				animation-delay: calc(.1s)
+			}
+
+			.delay-2 {
+				animation-delay: calc(.2s)
+			}
+
+			.delay-3 {
+				animation-delay: calc(.3s)
+			}
+
+			.delay-4 {
+				animation-delay: calc(.4s)
+			}
+
+			.delay-5 {
+				animation-delay: calc(.5s)
+			}
+
+			.delay-6 {
+				animation-delay: calc(.6s)
+			}
+
+			.delay-7 {
+				animation-delay: calc(.7s)
+			}
+
+			.delay-8 {
+				animation-delay: calc(.8s)
+			}
+
+			.delay-9 {
+				animation-delay: calc(.9s)
+			}
+
+		}
+
+		&:before,
+		&:after {
+			flex-shrink: 0;
+			flex-grow: 0;
+			content: '';
+			width: 20%;
+			height: 5rpx;
+			border-radius: 8rpx;
+			background-color: #898989;
+		}
+	}
+
+	.is_loading {
+		animation: breathe 1s ease-in-out 0s infinite alternate;
+	}
+
+	.text_loading {
+		animation: jump 2s ease-in-out infinite;
+	}
+}
+
+@keyframes breathe {
+	from {
+		opacity: 1;
+	}
+
+	to {
+		opacity: .5;
+	}
+}
+
+@keyframes jump {
+
+	30% {
+		transform: translateY(-12rpx)
+	}
+
+	60% {
+		transform: translateY(12rpx)
+	}
+
+	0%,
+	100% {
+		transform: translateY(0rpx)
+	}
+}
+</style>

+ 103 - 0
components/LoadMore/LoadMore.vue

@@ -0,0 +1,103 @@
+<template>
+  <view
+    class="loading-more-container"
+    v-show="['loading', 'no-more'].includes(status)"
+  >
+    <div id="loading-wrapper" v-show="status === 'loading'">
+      <div class="dot"></div>
+      <div class="dot"></div>
+      <div class="dot"></div>
+      <div class="dot"></div>
+      <div class="dot"></div>
+    </div>
+
+    <view class="no-more" v-show="status === 'no-more'">{{ text }}</view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    status: {
+      type: String,
+      default: 'loading',
+      validate(value) {
+        if (!['loading', 'no-more'].includes(value)) {
+          console.warn('loading的取值必须是loading或者no-more')
+        }
+        return true
+      },
+    },
+
+    text: {
+      type: String,
+      default: '没有更多了',
+    },
+  },
+
+  watch: {
+    status(value) {
+      console.log(value)
+    },
+  },
+}
+</script>
+
+<style lang="less" scoped>
+.loading-more-container {
+  width: 100%;
+  height: 60upx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.dot {
+  width: 4px;
+  height: 4px;
+  border-radius: 2px;
+  background: rgb(250, 81, 81);
+  float: left;
+  margin: 0 3px;
+  animation: dot linear 1s infinite;
+  -webkit-animation: dot linear 1s infinite;
+}
+.dot:nth-child(1) {
+  animation-delay: 0s;
+}
+.dot:nth-child(2) {
+  animation-delay: 0.15s;
+}
+.dot:nth-child(3) {
+  animation-delay: 0.3s;
+}
+.dot:nth-child(4) {
+  animation-delay: 0.45s;
+}
+.dot:nth-child(5) {
+  animation-delay: 0.6s;
+}
+@keyframes dot {
+  0%,
+  60%,
+  100% {
+    transform: scale(1);
+  }
+  30% {
+    transform: scale(2.5);
+  }
+}
+@-webkit-keyframes dot {
+  0%,
+  60%,
+  100% {
+    transform: scale(1);
+  }
+  30% {
+    transform: scale(2.5);
+  }
+}
+
+.no-more {
+  color: #ccc;
+}
+</style>

+ 257 - 0
components/Loading/index.vue

@@ -0,0 +1,257 @@
+<template>
+	<view class="loading_box mask flex-center" @touchmove.stop.prevent="HandlePageMove">
+		<!--  点动画  -->
+		<view v-if="false" class="dot_box theOuterRotation">
+			<view v-for="item in 4" class="dot_item"></view>
+		</view>
+
+		<!-- 进度条动画 -->
+		<view v-if="false" class="progress_box">
+			<view class="progress_item progress_roll_center"></view>
+		</view>
+		Loading....
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'Index',
+	data() {
+		return {}
+	},
+	onPageScroll(e) {
+		return false
+	},
+
+	mounted() {
+		// #ifdef H5
+		// #endif
+	},
+
+	methods: {
+		HandlePageMove() {
+			return false
+		}
+	}
+}
+</script>
+
+<style lang="scss"
+       scoped
+>
+.mask {
+	width: 100vw;
+	height: 100vh;
+	background: rgba(0, 0, 0, 0.5);
+	color: #fff;
+}
+
+.flex-center {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex-direction: column;
+}
+
+.loading_box {
+	width: 100vw;
+	height: 100vh;
+	position: fixed;
+	top: 0;
+	bottom: 0;
+	z-index: 9999;
+
+	/** 点动画*/
+	.theOuterRotation {
+		animation: rotate 2s ease-in-out .5s infinite normal;
+	}
+
+	.dot_box {
+		width: 80rpx;
+		height: 80rpx;
+		//background: #fff;
+		border-radius: 20rpx;
+		margin: 20rpx 0;
+		position: relative;
+
+		// 原始dot
+		.dot_item {
+			width: 30rpx;
+			height: 30rpx;
+			background: #fff;
+			border-radius: 50%;
+			position: absolute;
+			transition: all .6s;
+			box-shadow: 0 0 2rpx #b4b4b4;
+
+			&:nth-child(1) {
+				top: 0;
+				left: 0;
+			}
+
+			&:nth-child(2) {
+				top: 0;
+				right: 0;
+			}
+
+			&:nth-child(3) {
+				bottom: 0;
+				right: 0;
+			}
+
+			&:nth-child(4) {
+				bottom: 0;
+				left: 0;
+			}
+		}
+
+		// 单双放大动画
+		.dot_scale {
+			&:nth-child(1) {
+				top: 0;
+				left: 0;
+				animation: magnify 2s ease-in-out 0s infinite alternate;
+			}
+
+			&:nth-child(2) {
+				top: 0;
+				right: 0;
+				animation: magnify 2s ease-in-out 1s infinite alternate;
+			}
+
+			&:nth-child(3) {
+				bottom: 0;
+				right: 0;
+				animation: magnify 2s ease-in-out 0s infinite alternate;
+			}
+
+			&:nth-child(4) {
+				bottom: 0;
+				left: 0;
+				animation: magnify 2s ease-in-out 1s infinite alternate;
+			}
+		}
+
+		// 单点移动动画
+		.dot_move {
+			&:nth-child(1) {
+				z-index: 2;
+				animation: moveDot 2s ease-in-out 0s infinite normal;
+			}
+		}
+
+		// 单偶放大
+		@keyframes magnify {
+			0% {
+				transform: scale(1);
+				background: rgba(0, 255, 228, 0.82);
+			}
+
+			25% {
+				background: #32b5cc;
+			}
+
+			50% {
+				transform: scale(1.4);
+				background: #73e34e;
+			}
+
+			75% {
+				background: #0ec469;
+			}
+
+			100% {
+				transform: scale(1);
+				background: #868686;
+			}
+		}
+
+		// 移动单点
+		@keyframes moveDot {
+			0% {
+				top: 0;
+				left: 0;
+				background: #d0f598;
+			}
+
+			25% {
+				top: 0;
+				left: 100%;
+				transform: translateX(-100%);
+				background: #f5e298;
+			}
+
+			50% {
+				top: 100%;
+				left: 100%;
+				transform: translate(-100%, -100%);
+				background: #6bea91;
+			}
+
+			75% {
+				top: 100%;
+				left: 0;
+				transform: translateY(-100%);
+				background: #e84c7a;
+			}
+
+			100% {
+				top: 0;
+				left: 0;
+			}
+
+		}
+
+		@keyframes rotate {
+			0% {
+				transform: rotate(0deg);
+			}
+
+			100% {
+				transform: rotate(360deg);
+			}
+		}
+	}
+
+	/** 进度条动画*/
+	.progress_box {
+		width: 300rpx;
+		height: 25rpx;
+		margin: 20rpx 0;
+		border-radius: 25rpx;
+		background-color: #fff;
+		position: relative;
+		overflow: hidden;
+
+		.progress_item {
+			width: 0%;
+			height: 100%;
+			background: #f68686;
+			border-radius: 25rpx;
+
+		}
+
+		.progress_roll {
+			animation: roll 1s ease-in-out 0s infinite alternate-reverse;
+		}
+
+		.progress_roll_center {
+			width: 5%;
+			transition: all .6s;
+			margin: 0 auto;
+			animation: roll 1s cubic-bezier(.15, .2, .05, .4) 0s infinite alternate-reverse;
+		}
+	}
+
+	@keyframes roll {
+		from {
+			width: 5%;
+		}
+
+		to {
+			width: 100%;
+		}
+	}
+
+}
+</style>

+ 126 - 0
components/NewGoods/NewGoods.vue

@@ -0,0 +1,126 @@
+<template>
+	<view
+		v-if="data" class="goods-container" :style="{ width: width * 2 + 'upx' }"
+		@click="$jump('/pages/prod/prod?goodsId=' + data.id)"
+	>
+		<tui-lazyload-img
+			width="100%" :height="height" mode="scaleToFill" radius="20rpx 20rpx 0 0"
+			:src="common.seamingImgUrl(data.picUrl)"
+		></tui-lazyload-img>
+
+		<view class="goods-info">
+			<view class="goods-name hiden-text">
+				{{ data.name }}
+			</view>
+
+			<view v-if="showTags" class="tags">
+				<view class="tag">七天无理由退货</view>
+				<view class="tag">品牌正品</view>
+			</view>
+
+			<view v-if="showDetailBtn" class="item">
+				<view class="price-text">
+					¥<text>{{ data.counterPrice }}</text>
+				</view>
+				<button class="bee-btn">查看详情</button>
+			</view>
+			<view v-else>
+				<view class="price-text">
+					¥<text>{{ data.counterPrice }}</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		width: {
+			type: Number,
+			default: 174
+		},
+		height: {
+			type: String,
+			default: '300rpx'
+		},
+
+		data: {
+			type: Object,
+			required: true
+		},
+
+		showTags: {
+			type: Boolean,
+			default: true
+		},
+
+		showDetailBtn: {
+			type: Boolean,
+			default: true
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.goods-container {
+	// border-radius: 20upx;
+	// overflow: hidden;
+	background-color: #fff;
+
+	.goods-info {
+		padding: 14upx 22upx;
+		box-sizing: border-box;
+		background-color: #fff;
+
+		.goods-name {
+			color: #3d3d3d;
+			font-size: 24upx;
+		}
+		.price-text {
+			color: #fa5151;
+			font-weight: bold;
+
+			text {
+				font-size: 36upx;
+			}
+		}
+	}
+
+	.tags {
+		display: flex;
+		align-items: center;
+		justify-content: flex-start;
+		font-size: 20upx;
+		color: #777;
+		margin: 12upx 0 26upx 0;
+
+		.tag {
+			padding: 2upx 6upx;
+			border: 1upx solid #777;
+			white-space: nowrap;
+			border-radius: 10upx;
+			font-size: 20upx;
+
+			&:nth-child(1) {
+				margin-right: 20upx;
+			}
+		}
+	}
+
+	.item {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+
+		.bee-btn {
+			color: #fff;
+			font-size: 24upx;
+			background-color: #fa5151;
+			padding: 6upx 14upx;
+			border-radius: 10upx;
+		}
+	}
+}
+</style>

+ 49 - 0
components/NoMore/NoMore.vue

@@ -0,0 +1,49 @@
+<template>
+	<view v-if="show" class="no-more-content">
+		<view class="line" />
+		<view class="text">
+			<slot>没有更多了</slot>
+		</view>
+		<view class="line" />
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'NoMore',
+	props: {
+		show: {
+			type: Boolean,
+			default: false
+		}
+	},
+	data() {
+		return {}
+	},
+	methods: {}
+}
+</script>
+
+<style
+    lang="scss"
+    scoped
+>
+.no-more-content {
+	margin-top: 35rpx;
+	width: 100%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+
+	.line {
+		width: 20vw;
+		height: 2rpx;
+		background-color: #808080;
+	}
+
+	.text {
+		color: #808080;
+		margin: 0 50rpx;
+	}
+}
+</style>

+ 59 - 0
components/OrderInfo/OrderInfo.vue

@@ -0,0 +1,59 @@
+<template>
+	<view class="order-info-container">
+		<view style="color: #666666;">订单编号:{{ data.orderFormid }}</view>
+		<!-- <view style="margin-top: 20upx;padding: 20upx;font-size: 26upx;background-color: #eeeeee;">
+			<view>收货人:{{ data.consignee }}-{{ data.mobile }}</view>
+			<view>{{ data.address }}</view>
+			<view v-if="data.message">留言:{{ data.message }}</view>
+			</view> -->
+		<view style="margin-top: 20upx;padding: 0 20upx;">
+			<view v-if="data.state">
+				订单状态:
+				<text v-if="data.state === 1">待付款</text>
+				<text v-else-if="data.state === 2">待发货</text>
+				<text v-else-if="data.state === 8">待核销</text>
+				<text v-else-if="data.state === 3">待收货</text>
+				<text v-else-if="data.state === 4">已完成</text>
+				<text v-else-if="(data.state === 5) && (data.collageId != 0)">拼团失败</text>
+				<text v-else-if="data.state === 5">已关闭</text>
+				<text v-else-if="data.state === 6">待成团</text>
+				<text v-else-if="data.state === 7">待售后</text>
+				<text v-else>--</text>
+			</view>
+			<view v-if="data.shopName">商户:{{ data.shopName }}</view>
+			<view v-if="data.logisticsPrice">快递运费:¥{{ data.logisticsPrice }}</view>
+			<view v-if="data.discountPrice">平台优惠:-¥{{ data.discountPrice }}</view>
+		</view>
+		<view style="padding: 0 20upx;">
+			<view style="display: flex;justify-content: space-between;">
+				<view v-if="typeof data.orderPrice === 'number'">商品总价:<text style="color: red;">¥{{ data.orderPrice }}</text></view>
+				<view v-if="typeof data.price === 'number'">实付款:<text style="color: red;">¥{{ data.price }}</text></view>
+			</view>
+			<view style="margin-top: 12upx;padding-top: 12upx;;border-top: 1px solid #dddddd;">
+				<view style="display: flex;justify-content: space-between;font-size: 26upx;color: #999999;">
+					<text>创建时间:</text>
+					<text>{{ data.createTime }}</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		data: {
+			type: Object,
+			required: true
+		}
+	},
+
+	methods: {
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.order-info-container {
+}
+</style>

+ 101 - 0
components/SearchBar/SearchBar.vue

@@ -0,0 +1,101 @@
+<template>
+	<view
+		class="search-bar-container" :style="{
+			height,
+			borderColor: color,
+			background
+		}" @click="handleClick"
+	>
+		<BeeIcon :src="require('./search.png')"></BeeIcon>
+		<input v-model="content" style="font-size: 24upx;" type="text" placeholder="输入您想搜索的商品" />
+
+		<button class="bee-btn" :style="{ background: color }" @click="handleClickBtn">
+			{{ btnText }}
+		</button>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'SearchBar',
+	props: {
+		prevent: {
+			type: Boolean,
+			default: false
+		},
+
+		height: {
+			type: String,
+			default: '72upx'
+		},
+
+		color: {
+			type: String,
+			default: '#FA5151'
+		},
+
+		btnText: {
+			type: String,
+			default: '搜索'
+		},
+		background: {
+			type: String
+		}
+	},
+	data() {
+		return {
+			content: ''
+		}
+	},
+	methods: {
+		handleClick() {
+			if (this.prevent) {
+				this.$emit('click')
+			}
+		},
+		handleClickBtn() {
+			if (!this.prevent) {
+				this.$emit('click-btn', this.content)
+			}
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.search-bar-container {
+	display: flex;
+	align-items: center;
+	justify-content: flex-start;
+	position: relative;
+	border: 2upx solid #fa5151;
+	padding: 16upx 122upx 16upx 16upx;
+	box-sizing: border-box;
+	border-radius: 100px;
+
+	input {
+		flex: 1;
+		color: #999999;
+		margin-left: 14upx;
+	}
+
+	.bee-btn {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		position: absolute;
+		color: #f9f9f9;
+		right: 0;
+		top: -2upx;
+		width: 120upx;
+		height: 72upx;
+		font-size: 28upx;
+		border-radius: 100px;
+		transition: all 350ms;
+
+		&:active {
+			opacity: 0.7;
+		}
+	}
+}
+</style>

TEMPAT SAMPAH
components/SearchBar/search.png


+ 232 - 0
components/Skeleton/index.vue

@@ -0,0 +1,232 @@
+<template>
+  <view
+      v-if="loading"
+      :style="{
+		width: windowWinth + 'px',
+		height: windowHeight + 'px',
+		backgroundColor: bgColor,
+		position: 'absolute',
+		left: left + 'px',
+		top: isFixedOnFather?'0px':top + 'px',
+		zIndex: 100,
+		overflow: 'hidden'
+	}"
+      @touchmove.stop.prevent
+  >
+
+    <view
+        v-for="(item, index) in RectNodes"
+        :key="$u.guid()"
+        :class="[animation ? 'skeleton-fade' : '']"
+        :style="{
+			width: item.width + 'px',
+			height: item.height + 'px',
+			backgroundColor: elColor,
+			position: 'absolute',
+			left: (item.left - left) + 'px',
+			top:(item.top - top)<0?(item.top - top +windowHeight-80)+ 'px':(item.top - top) + 'px'
+		}"
+    ></view>
+    <view
+        v-for="(item, index) in circleNodes"
+        :key="$u.guid()"
+        :class="animation ? 'skeleton-fade' : ''"
+        :style="{
+			width: item.width + 'px',
+			height: item.height + 'px',
+			backgroundColor: elColor,
+			borderRadius: item.width/2 + 'px',
+			position: 'absolute',
+			left: (item.left - left) + 'px',
+			top:(item.top - top)<0?(item.top - top +windowHeight-80)+ 'px':(item.top - top) + 'px'
+		}"
+    ></view>
+    <view
+        v-for="(item, index) in filletNodes"
+        :key="$u.guid()"
+        :class="animation ? 'skeleton-fade' : ''"
+        :style="{
+			width: item.width + 'px',
+			height: item.height + 'px',
+			backgroundColor: elColor,
+			borderRadius: borderRadius + 'rpx',
+			position: 'absolute',
+			left: (item.left - left) + 'px',
+			top:(item.top - top)<0?(item.top - top +windowHeight-80)+ 'px':(item.top - top) + 'px'
+		}"
+    >
+    </view>
+  </view>
+</template>
+
+<script>
+/**
+ * skeleton 骨架屏
+ * @description 骨架屏一般用于页面在请求远程数据尚未完成时,页面用灰色块预显示本来的页面结构,给用户更好的体验。
+ * @tutorial https://www.uviewui.com/components/skeleton.html
+ * @property {String} el-color 骨架块状元素的背景颜色(默认#e5e5e5)
+ * @property {String} bg-color 骨架组件背景颜色(默认#ffffff)
+ * @property {Boolean} animation 骨架块是否显示动画效果(默认false)
+ * @property {String Number} border-radius u-skeleton-fillet类名元素,对应的骨架块的圆角大小,单位rpx(默认10)
+ * @property {Boolean} loading 是否显示骨架组件,请求完成后,将此值设置为false(默认true)
+ * @property {Boolean} isFixedOnFather 是否吸住父组件顶部(应用于子组件情况),父组件需要有定位
+ * @example <u-skeleton :loading="true" :animation="true"></u-skeleton>
+ */
+export default {
+  name: "u-skeleton",
+  props: {
+    // 需要渲染的元素背景颜色,十六进制或者rgb等都可以
+    elColor: {
+      type: String,
+      default: '#e5e5e5'
+    },
+    // 整个骨架屏页面的背景颜色
+    bgColor: {
+      type: String,
+      default: '#ffffff'
+    },
+    // 是否显示加载动画
+    animation: {
+      type: Boolean,
+      default: true
+    },
+    // 圆角值,只对类名为u-skeleton-fillet的元素生效,为数值,不带单位
+    borderRadius: {
+      type: [String, Number],
+      default: "10"
+    },
+    // 是否显示骨架,true-显示,false-隐藏
+    loading: {
+      type: Boolean,
+      default: true
+    },
+    // 是否吸住父组件顶部
+    isFixedOnFather: {
+      type: Boolean,
+      default: true
+    },
+    marginTop: {
+      type: Number,
+      default: 0
+    }
+  },
+  data() {
+    return {
+      windowWinth: 750, // 骨架屏宽度
+      windowHeight: 1500, // 骨架屏高度
+      filletNodes: [], // 圆角元素
+      circleNodes: [], // 圆形元素
+      RectNodes: [], // 矩形元素
+      top: 0,
+      left: 0,
+    }
+  },
+  methods: {
+    // 查询各节点的信息
+    selecterQueryInfo() {
+      // 获取整个父组件容器的高度,当做骨架屏的高度
+      // 在微信小程序中,如果把骨架屏放入组件中使用的话,需要调in(this)上下文为父组件才有效
+      let query = '';
+      // #ifdef MP-WEIXIN
+      query = uni.createSelectorQuery().in(this.$parent);
+      // #endif
+      // #ifndef MP-WEIXIN
+      query = uni.createSelectorQuery()
+      // #endif
+      query.selectAll('.u-skeleton').boundingClientRect().exec((res) => {
+        this.windowHeight = res[0][0].height;
+        this.windowWinth = res[0][0].width;
+        res[0][0].bottom = res[0][0].height
+        this.top = this.marginTop + res[0][0].top;
+        this.left = res[0][0].left;
+      });
+      // 矩形骨架元素
+      this.getRectEls();
+      // 圆形骨架元素
+      this.getCircleEls();
+      // 圆角骨架元素
+      this.getFilletEls();
+    },
+    // 矩形元素列表
+    getRectEls() {
+      let query = '';
+      // 在微信小程序中,如果把骨架屏放入组件中使用的话,需要调in(this)上下文为父组件才有效
+      // #ifdef MP-WEIXIN
+      query = uni.createSelectorQuery().in(this.$parent);
+      // #endif
+      // #ifndef MP-WEIXIN
+      query = uni.createSelectorQuery()
+      // #endif
+      query.selectAll('.u-skeleton-rect').boundingClientRect().exec((res) => {
+        this.RectNodes = res[0];
+      });
+    },
+    // 圆角元素列表
+    getFilletEls() {
+      let query = '';
+      // 在微信小程序中,如果把骨架屏放入组件中使用的话,需要调in(this)上下文为父组件才有效
+      // #ifdef MP-WEIXIN
+      query = uni.createSelectorQuery().in(this.$parent);
+      // #endif
+      // #ifndef MP-WEIXIN
+      query = uni.createSelectorQuery()
+      // #endif
+      query.selectAll('.u-skeleton-fillet').boundingClientRect().exec((res) => {
+        this.filletNodes = res[0];
+      });
+    },
+    // 圆形元素列表
+    getCircleEls() {
+      let query = '';
+      // 在微信小程序中,如果把骨架屏放入组件中使用的话,需要调in(this)上下文为父组件才有效
+      // #ifdef MP-WEIXIN
+      query = uni.createSelectorQuery().in(this.$parent);
+      // #endif
+      // #ifndef MP-WEIXIN
+      query = uni.createSelectorQuery()
+      // #endif
+      query.selectAll('.u-skeleton-circle').boundingClientRect().exec((res) => {
+        this.circleNodes = res[0];
+      });
+    }
+  },
+  // 组件被挂载
+  mounted() {
+    // 获取系统信息
+    let systemInfo = uni.getSystemInfoSync();
+    this.windowHeight = systemInfo.windowHeight;
+    this.windowWinth = systemInfo.windowWidth;
+    this.selecterQueryInfo();
+  }
+}
+</script>
+
+<style
+    lang="scss"
+    scoped
+>
+
+.skeleton-fade {
+  width: 100%;
+  height: 100%;
+  background: rgb(194, 207, 214);
+  animation-duration: 1.5s;
+  animation-name: blink;
+  animation-timing-function: ease-in-out;
+  animation-iteration-count: infinite;
+}
+
+@keyframes blink {
+  0% {
+    opacity: 1;
+  }
+
+  50% {
+    opacity: 0.4;
+  }
+
+  100% {
+    opacity: 1;
+  }
+}
+</style>

+ 143 - 0
components/StoreGoods/StoreGoods.vue

@@ -0,0 +1,143 @@
+<template>
+	<view v-if="goodsData" class="store-goods-container">
+		<view style="max-height: 440upx;min-height: 120upx;overflow: hidden;">
+			<tui-lazyload-img
+				class="goods-img" :mode="picMode" :width="picWidth" :height="picHeight"
+				:src="common.seamingImgUrl(goodsData.picUrl)" @click="$emit('click-content', goodsData)"
+			></tui-lazyload-img>
+		</view>
+
+		<view style="flex: 1;width: 0;padding: 0 20upx;font-size: 32upx;">
+			<view class="store-goods-name" @click="$emit('click-content', goodsData)">
+				{{ goodsData.name }}
+			</view>
+
+			<view @click="$emit('click-content', goodsData)">
+				<view
+					v-if="showMsg"
+					style="display: flex;justify-content: space-between;padding: 14upx 0 0;font-size: 26upx;color: #777777;"
+				>
+					<text>{{ goodsData.browse }}浏览量</text>
+					<text>销量 {{ goodsData.sales }}</text>
+				</view>
+				<view v-if="showSn">商品编号:{{ goodsData.goodsSn }}</view>
+				<view
+					v-if="showVoucher && goodsData.supportVoucher"
+					style="width: fit-content;margin-top: 8upx;margin-right: 8upx;padding: 4upx 8upx;border: 0.25px solid #51cc46;border-radius: 12upx;font-size: 28upx;font-weight: bold;color: #51cc46;"
+				>
+					支持代金券
+				</view>
+			</view>
+
+			<view class="time">
+				<view class="wrapper" @click="$emit('click-content', goodsData)">
+					<view v-if="showTag && goodsData.isHot" class="price-tag">热卖</view>
+					<view class="price-text">¥{{ goodsData.counterPrice }}</view>
+				</view>
+
+				<!-- <BeeIcon
+					v-if="showIcon" :size="28" :src="require('../../static/brand/detail/add-icon.png')"
+					@click="$emit('add-car', goodsData)"
+					></BeeIcon> -->
+				<view v-if="showIcon" style="padding: 10upx;background-color: #ffe500;border-radius: 50%;line-height: 1;">
+					<tui-icon name="plus" color="#000000" size="28" unit="upx" bold @click="$emit('add-car', goodsData)"></tui-icon>
+				</view>
+				<slot name="button"></slot>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'StoreGoods',
+	props: {
+		goodsData: {
+			type: Object,
+			required: true
+		},
+		picMode: {
+			type: String,
+			default: 'widthFix'
+		},
+		picWidth: {
+			type: String,
+			default: '210rpx'
+		},
+		picHeight: {
+			type: String,
+			default: 'auto'
+		},
+		showIcon: {
+			type: Boolean,
+			default: true
+		},
+		showTag: {
+			type: Boolean,
+			default: true
+		},
+		showMsg: {
+			type: Boolean,
+			default: true
+		},
+		showSn: {
+			type: Boolean,
+			default: false
+		},
+		showVoucher: {
+			type: Boolean,
+			default: false
+		}
+	},
+
+	methods: {
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.store-goods-container {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	width: 100%;
+	margin-bottom: 15upx;
+	box-sizing: border-box;
+
+	.store-goods-name {
+		color: #3d3d3d;
+		font-weight: bold;
+		// width: 340upx;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+	}
+
+	.time {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		flex-wrap: wrap;
+		margin: 10upx 0 18upx;
+
+		.wrapper {
+			.price-text {
+				color: #fa5151;
+				font-weight: bold;
+				font-size: 28upx;
+			}
+
+			.price-tag {
+				width: fit-content;
+				margin-bottom: 4upx;
+				padding: 0 10upx;
+				border: 1upx solid #fa5151;
+				font-size: 20upx;
+				color: #fa5151;
+				border-radius: 8upx;
+				margin-left: 9upx;
+			}
+		}
+	}
+}
+</style>

+ 92 - 0
components/StoreGoodsList/StoreGoodsList.vue

@@ -0,0 +1,92 @@
+<template>
+	<view class="store-goods-list-container">
+		<view
+			v-if="brandDetail.categoryList && brandDetail.categoryList.length"
+			style="display: flex;box-sizing: border-box;"
+		>
+			<view style="background-color: #f3f3f3;max-height: 85vh;" :style="{ overflowY }">
+				<view
+					v-for="item in brandDetail.categoryList" :key="item.serverNameOne"
+					style="max-width: 144upx;padding: 20upx 28upx;word-break: break-all;box-sizing: border-box;"
+					:style="{ backgroundColor: item.id === currentTab ? '#ffffff' : 'transparent' }"
+					@click="(currentTab = item.id) && (currentGoods = brandDetail.categoryList.find(part => part.id === item.id).goodsList || [])"
+				>
+					{{ item.name }}
+				</view>
+			</view>
+			<view style="flex: 1;padding: 20upx;max-height: 85vh;" :style="{ overflowY }">
+				<view v-if="currentGoods && currentGoods.length">
+					<view v-for="item in currentGoods" :key="item.id">
+						<StoreGoods
+							:goods-data="item" pic-height="210rpx" pic-mode="aspectFit" show-voucher
+							@click-content="(e) => $emit('click-content', e)" @add-car="(e) => $emit('add-car', e)"
+						></StoreGoods>
+					</view>
+				</view>
+				<view v-else style="margin: 40upx 0;text-align: center;">
+					暂无商品~
+				</view>
+			</view>
+		</view>
+		<view v-else class="no-data">
+			暂无数据
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'StoreGoodsList',
+	props: {
+		brandDetail: {
+			type: Object,
+			required: true
+		},
+		overflowY: {
+			type: String,
+			default: 'auto'
+		}
+	},
+
+	data() {
+		return {
+			currentTab: '',
+			currentGoods: []
+		}
+	},
+
+	watch: {
+		brandDetail: {
+			handler(newVal) {
+				if (newVal) {
+					if (this.brandDetail.categoryList && this.brandDetail.categoryList.length) {
+						(this.currentTab = this.brandDetail.categoryList[0].id) && (this.currentGoods = this.brandDetail.categoryList[0].goodsList || [])
+					}
+				}
+			},
+			immediate: true,
+			deep: true
+		}
+	},
+
+	methods: {
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.store-goods-list-container {
+	width: 100%;
+	font-size: 32upx;
+
+	.no-data {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		min-height: 200upx;
+		color: #ccc;
+		padding: 20upx 0;
+		flex-direction: column;
+	}
+}
+</style>

+ 127 - 0
components/VoucherUse/VoucherUse.vue

@@ -0,0 +1,127 @@
+<template>
+	<view>
+		<view class="line-pane" @click="drawerVisible = true">
+			<view style="font-size: 28upx;" class="title">代金券</view>
+			<view style="display: flex;align-items: center;">
+				<view class="desc" style="color: #999999">{{ voucherName }}</view>
+				<tui-icon name="arrowright" size="22" color="#979797" style="padding: 2upx 0 2upx 8upx;"></tui-icon>
+			</view>
+		</view>
+		<tui-drawer mode="bottom" :visible="drawerVisible" @close="drawerVisible = false">
+			<view style="height: 55vh;padding: 20upx;overflow-y: auto;">
+				<view v-if="voucherList && voucherList.length">
+					<view style="text-align: right;">
+						<tui-radio-group :value="String(voucherSelected)" style="" @change="handleRadioChange">
+							<tui-label>
+								<tui-list-cell padding="16upx">
+									<view>
+										<text style="padding-right: 10upx;">不使用</text>
+										<tui-radio value="0" color="#e25531" border-color="#999"></tui-radio>
+									</view>
+								</tui-list-cell>
+							</tui-label>
+						</tui-radio-group>
+
+						<!-- <text>不使用</text>
+							<tui-radio :checked="false" :value="part.value" color="#07c160" border-color="#999">
+							</tui-radio> -->
+					</view>
+					<view>
+						<view v-for="item in voucherList" :key="item.id" class="item">
+							<view style="display: flex;justify-content: space-between;align-items: center;padding: 20upx;margin: 20upx;border: 1upx solid #b1b0b0;border-radius: 12upx;">
+								<view style="flex: 1;">
+									<view style="display: flex;justify-content: space-between;">
+										<text>{{ item.voucherName }}</text>
+										<text style="padding-right: 50upx;color: #b1b0b0;">{{ item.ratio }} : 1</text>
+									</view>
+									<view>{{ item.desc }}</view>
+								</view>
+								<tui-button
+									type="warning" width="120rpx" height="50rpx" shape="circle"
+									:disabled="item.id === voucherSelected" @click="handleChooseVoucher(item)"
+								>
+									选择
+								</tui-button>
+							</view>
+						</view>
+					</view>
+				</view>
+				<view v-else>
+					<tui-no-data>暂无代金券</tui-no-data>
+				</view>
+			</view>
+		</tui-drawer>
+	</view>
+</template>
+
+<script>
+
+export default {
+	name: 'CouponUse',
+	props: {
+		voucherList: {
+			type: Array,
+			default: () => []
+		}
+	},
+	data() {
+		return {
+			drawerVisible: false,
+			voucherName: '暂无代金券可用',
+			voucherSelected: 0
+		}
+	},
+	watch: {
+		voucherList: {
+			handler(newVal) {
+				if (newVal && newVal.length && !this.voucherSelected) {
+					this.voucherName = '不使用'
+					this.voucherSelected = 0
+				} else if (!newVal.length) {
+					this.voucherName = '暂无代金券可用'
+					this.voucherSelected = 0
+				}
+			},
+			immediate: true,
+			deep: true
+		}
+	},
+	methods: {
+		handleReset() {
+			this.voucherName = '不使用'
+			this.voucherSelected = 0
+			this.drawerVisible = false
+		},
+		handleRadioChange(e) {
+			console.log(e)
+			this.voucherName = '不使用'
+			if (this.voucherSelected !== Number(e.detail.value)) {
+				this.voucherSelected = Number(e.detail.value)
+				this.$emit('choose', { id: this.voucherSelected })
+			}
+			this.drawerVisible = false
+		},
+		handleChooseVoucher(item) {
+			console.log(item)
+			this.voucherName = item.voucherName
+			this.voucherSelected = item.id
+			this.$emit('choose', { id: this.voucherSelected })
+			this.drawerVisible = false
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.line-pane {
+	box-sizing: border-box;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	height: 100upx;
+	margin: 20upx 0;
+	padding: 20upx 18upx;
+	background-color: #fff;
+
+}
+</style>

+ 672 - 0
components/activities/combinedSales.vue

@@ -0,0 +1,672 @@
+<template>
+	<view v-if="selectComposeData && selectComposeData.length > 0" class="group-list">
+		<view class="group-warp">
+			<view class="title">
+				<label>
+					<image class="title-img" src="../../static/images/origin/combinationIcon.png" alt="组合销售" mode="widthFix">
+					</image>
+				</label>
+				<view class="price-text">
+					组合价:¥{{ composePrice }}
+				</view>
+			</view>
+			<view>
+				<scroll-view class="tabs-nav" scroll-x="true">
+					<view class="ul">
+						<view
+							v-for="(item, index) in selectComposeData" :key="index" class="li" :class="tabIndex === index && 'on'"
+							@click="tabChange(index)"
+						>
+							{{ item.composeName }}
+						</view>
+					</view>
+				</scroll-view>
+				<view
+					v-for="(item, index) in selectComposeData" :key="index" class="tabs-item"
+					:class="tabIndex === index && 'on'"
+				>
+					<swiper
+						class="swiper pro-box" :indicator-dots="false" :autoplay="true"
+						:display-multiple-items="item.composeProductInfoList.length < 3 ? item.composeProductInfoList.length : 3"
+						:disable-touch="item.composeProductInfoList.length <= 3" @change="swiperChange"
+					>
+						<swiper-item v-for="(itemJ, indexJ) in item.composeProductInfoList" :key="indexJ" class="pro-item-warp">
+							<view class="pro-item-inner">
+								<view class="pro-item">
+									<view class="pro-item-img">
+										<image class="img" :src="itemJ.productImage"></image>
+									</view>
+									<view class="pro-item-info">
+										<h3 class="name">
+											{{ itemJ.productName }}
+										</h3>
+										<view class="sku" @click.stop="changeSkuItemValue(itemJ, indexJ)">
+											<view class="text">{{ itemJ.skuItem.skuName }}</view>
+										</view>
+									</view>
+								</view>
+							</view>
+						</swiper-item>
+					</swiper>
+					<view v-if="item.composeProductInfoList.length > 3" class="swiper-dots">
+						<text
+							v-for="(dot, index) in item.composeProductInfoList.length - 2" :key="index" class="dot"
+							:class="{ 'dot-active': swiperCurrent === index }"
+						></text>
+					</view>
+				</view>
+				<view class="btn-buy" @click="doBuy">立即购买</view>
+			</view>
+		</view>
+		<!-- 商品详情 -->
+		<u-popup v-model="goodsDetailShowFlag" mode="bottom" border-radius="14">
+			<view class="goosDetailshow-box">
+				<view class="detailImg-box flex-row-plus">
+					<image class="detailImg" :src="selectedSku.image"></image>
+					<view class="flex-column-plus mar-left-40">
+						<view class="font-color-C5AA7B">
+							<label class="fs24">¥</label>
+							<label class="fs36 mar-left-10" v-text="getPrice(selectedSku)"></label>
+						</view>
+						<label class="fs24 font-color-999 mar-top-20">库存 {{ selectedSku.stockNumber }} 件</label>
+						<label class="fs24 mar-top-20">已选</label>
+					</view>
+				</view>
+				<view class="color-box flex-column-plus">
+					<view v-for="(attritem, index) in skuProData.names" :key="index" class="skuStyle">
+						<label class="fs24 font-color-999">{{ attritem.skuName }}</label>
+						<view class="colorName-box">
+							<view v-for="(attrRes, resIndex) in attritem.values" :key="resIndex" class="pad-bot-30">
+								<view
+									class="colorName"
+									:class="{ 'colorName-on': getselectedAttrVal(attritem.nameCode) == attrRes.valueCode }"
+									@click="nameCodeValueCodeClick(attritem.nameCode, attrRes.valueCode, true)"
+								>
+									{{ attrRes.skuValue }}
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+				<!--        <view class="goodsNum-box flex-row-plus flex-sp-between" :class="{'bottom-line' :supportHuabei}"> -->
+				<!--          <label class="font-color-999 fs24">数量</label> -->
+				<!--          <view class="goodsNum"> -->
+				<!--            <text class="subtract" @click="updateNumSub()">-</text> -->
+				<!--            <text class="goodsNumber" v-model="buyNum">{{buyNum}}</text> -->
+				<!--            <text class="add" @click.stop=" -->
+				<!--            ()">+</text> -->
+				<!--          </view> -->
+				<!--        </view> -->
+				<view class="goosDetailbut-box flex-items-plus">
+					<!--          <button type="default" @click="goodsDateils(shopId,productId,skuId)" >查看详情</button> -->
+					<button type="default" class="submitBtn" @click="submitBtn()">确认</button>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+import NET from '../../utils/request'
+import API from '../../config/api'
+
+export default {
+	name: 'CombinedSales',
+	props: {
+		pid: {
+			type: String,
+			default: ''
+		},
+		productData: {
+			type: Object,
+			default: () => { }
+		}
+	},
+	data() {
+		return {
+			skuShowFalg: false,
+			tabIndex: 0,
+			swiperCurrent: 0,
+			selectComposeData: [],
+			curProIndex: 0,
+			selectedSku: [],
+			selectedAttr: [],
+			skuProData: {},
+			goodsDetailShowFlag: false,
+			composePrice: 0
+		}
+	},
+	mounted() {
+		this.getSelectCompose()
+	},
+	methods: {
+		// 切换套餐
+		tabChange(index) {
+			this.tabIndex = index
+			this.calculatePrice()
+		},
+		// 滑动回调方法
+		swiperChange(e) {
+			this.swiperCurrent = e.detail.current
+		},
+		// 获取组合销售数据
+		getSelectCompose() {
+			NET.request(
+				API.selectCompose, {
+					productId: this.pid
+				},
+				'GET'
+			).then((res) => {
+				this.selectComposeData = res.data
+				for (let i = 0; i < this.selectComposeData.length; i++) {
+					const proList = this.selectComposeData[i].composeProductInfoList
+					for (let j = 0; j < proList.length; j++) {
+						proList[j].skuItem = proList[j].composeSkuInfoList[0]
+					}
+				}
+				this.calculatePrice()
+			})
+				.catch((res) => {
+
+				})
+		},
+		// 更换商品样式
+		changeSkuItemValue(item, index) {
+			this.curProIndex = index
+			uni.showLoading({
+				mask: true,
+				title: '加载中...'
+			})
+			NET.request(
+				API.QueryProductDetail, {
+					shopId: this.productData.shopId,
+					productId: item.productId,
+					skuId: item.skuItem.skuId,
+					terminal: 1
+				},
+				'GET'
+			).then((res) => {
+				uni.hideLoading()
+				this.skuProData = res.data
+				// 如果是单款式商品,需要特殊处理productData.names
+				const mapKeys = Object.keys(this.skuProData.map)
+				if (mapKeys.length === 1 && mapKeys[0] === '单款项') {
+					this.skuProData.names[0].values.push({
+						skuValue: '单款项',
+						valueCode: '单款项'
+					})
+				}
+
+				// 如果sku的图像为空,设置为商品的图像
+				for (var key in this.skuProData.map) {
+					const skuImage = this.skuProData.map[key].image
+					if (!skuImage) {
+						this.skuProData.map[key].image = this.skuProData.images[0]
+					}
+				}
+				this.goodsDetailShowFlag = true
+				this.selectBySkuId(item.skuItem.skuId)
+			})
+				.catch((res) => {
+					uni.hideLoading()
+				})
+		},
+		selectBySkuId(skuId) {
+			if (skuId) {
+				const mapinfo = this.skuProData.map
+				let flag = true
+				for (var key in mapinfo) {
+					if (parseInt(mapinfo[key].skuId) === parseInt(skuId)) {
+						flag = false
+						this.selectedSku = mapinfo[key]
+						// 选中sku对应的规格
+						const valueCodeList = key.split(',')
+						this.selectedAttr = []
+						this.skuProData.names.forEach((attr) => {
+							for (var index in attr.values) {
+								const valueCode = attr.values[index].valueCode
+								if (valueCodeList.includes(valueCode)) {
+									this.nameCodeValueCodeClick(attr.nameCode, valueCode, false)
+									break
+								}
+							}
+						})
+						break
+					}
+				}
+				// 匹配不上就赋值第一个
+				if (flag) {
+					for (var key in mapinfo) {
+						this.selectedSku = mapinfo[key]
+						break
+					}
+				}
+			}
+		},
+		nameCodeValueCodeClick(nameCode, valueCode, reSelectSku) {
+			this.selectedAttr[nameCode] = valueCode
+			if (reSelectSku) {
+				const attrList = []
+				for (var key in this.selectedAttr) {
+					attrList.push(this.selectedAttr[key])
+				}
+				const attrkey = attrList.join(',')
+				const mapinfo = this.skuProData.map
+				for (var key in mapinfo) {
+					if (attrkey === key) {
+						this.selectedSku = mapinfo[key]
+					}
+				}
+			}
+			this.$forceUpdate() // 重绘
+		},
+		// 提交更换商品规格
+		submitBtn() {
+			const curPro = this.selectComposeData[this.tabIndex].composeProductInfoList[this.curProIndex]
+			for (let i = 0; i < curPro.composeSkuInfoList.length; i++) {
+				if (curPro.composeSkuInfoList[i].skuId === this.selectedSku.skuId) {
+					this.selectedSku.skuName = curPro.composeSkuInfoList[i].skuName
+				}
+			}
+			curPro.skuItem = this.selectedSku
+			this.calculatePrice()
+			this.goodsDetailShowFlag = false
+		},
+		// 计算组合价
+		calculatePrice() {
+			const proList = this.selectComposeData[this.tabIndex].composeProductInfoList; const composeType = this.selectComposeData[this.tabIndex].composeType; const promote = this.selectComposeData[this.tabIndex].promote
+			let total = 0
+			for (let i = 0; i < proList.length; i++) {
+				total += this.getPrice(proList[i].skuItem)
+			}
+			switch (composeType) {
+				case 1:
+					this.composePrice = promote.toFixed(2)
+					break
+				case 2:
+					this.composePrice = (total - promote).toFixed(2)
+					break
+				case 3:
+					this.composePrice = parseFloat(total * promote / 10).toFixed(2)
+					break
+			}
+		},
+		// 根据活动显示不同价格
+		getPrice(item) {
+			// 所属活动 0-常规商品 1-拼团活动 2-秒杀活动 3-限时折扣活动 4-平台秒杀 5-平台折扣 6-定价捆绑 7-组合捆绑 8-场景营销 9-会员价
+			if (item.activityType) {
+				if (item.activityType === 0 || item.activityType === 6 || item.activityType === 7) {
+					return item.price
+				}
+				return item.originalPrice
+			}
+			return item.price
+		},
+		// 立即购买
+		doBuy() {
+			const addCart = []
+			const shopObj = {}
+			shopObj.shopId = this.productData.shopId
+			shopObj.composeId = this.selectComposeData[this.tabIndex].composeId
+			shopObj.skus = []
+			const proList = this.selectComposeData[this.tabIndex].composeProductInfoList
+			const len2 = proList.length
+			for (let j = 0; j < len2; j++) {
+				const skusObj = {}
+				skusObj.number = 1
+				skusObj.skuId = proList[j].skuItem.skuId
+				shopObj.skus.push(skusObj)
+			}
+			addCart.push(shopObj)
+			uni.setStorageSync('skuItemDTOList', addCart)
+			uni.navigateTo({
+				url: '../../pages_category_page1/orderModule/orderConfirm?type=1'
+			})
+		},
+		getselectedAttrVal(item) {
+			return this.selectedAttr[item]
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.group-list {
+	padding: 10upx 20upx 60upx;
+	border-top: 12upx solid #F8F8F8;
+
+	.group-warp {
+		height: 680upx;
+
+		background: #333333;
+		box-shadow: 0 20upx 30upx rgba(0, 0, 0, 0.3);
+		opacity: 1;
+		border-radius: 20upx;
+	}
+
+	.title {
+		display: flex;
+		align-items: center;
+		position: relative;
+		justify-content: space-between;
+		padding: 32upx 0 20upx 30upx;
+
+		.title-img {
+			width: 203upx;
+		}
+
+		.price-text {
+			padding: 0 34upx;
+			margin-right: 10upx;
+			height: 50upx;
+			background: linear-gradient(90deg, #C83732 0%, #E25C44 100%);
+			box-shadow: 0 6upx 12upx rgba(233, 0, 0, 0.3);
+			border-radius: 26upx;
+			font-size: 24upx;
+			color: #fff;
+			text-align: center;
+			line-height: 50upx;
+			margin-left: 20upx;
+
+			.swiper {
+				height: 50upx;
+			}
+		}
+	}
+
+	.tabs-nav {
+		padding: 0 10upx;
+		margin-bottom: 20upx;
+
+		.ul {
+			display: flex;
+
+			.li {
+				//flex: 1 0 auto;
+				text-align: center;
+				font-size: 26rpx;
+				color: #999;
+				position: relative;
+				padding: 12px 40px;
+				word-break: keep-all;
+
+				&:first-child {
+					margin-left: 0;
+				}
+
+				&.on {
+					color: #FFEBC4;
+
+					&:after {
+						content: '';
+						width: 100%;
+						height: 2rpx;
+						background: #FFEBC4;
+						position: absolute;
+						left: 0;
+						bottom: 0;
+					}
+				}
+			}
+		}
+	}
+
+	.tabs-item {
+		display: none;
+
+		&.on {
+			display: block;
+		}
+	}
+
+	.pro-box {
+		height: 318upx;
+		padding: 0 2upx 20upx;
+
+		.pro-item-inner {
+			padding: 0 8upx;
+			display: flex;
+			justify-content: center;
+		}
+
+		.pro-item {
+			width: 219upx;
+			background: #FFFFFF;
+			padding: 10upx;
+			height: 318upx;
+
+			.pro-item-img {
+				width: 100%;
+				height: 160upx;
+
+				.img {
+					width: 100%;
+					height: 100%;
+					object-fit: contain;
+
+				}
+			}
+
+			.pro-item-info {
+				.name {
+					font-size: 24upx;
+					line-height: 34upx;
+					color: #333333;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+					text-align: center;
+					font-weight: normal;
+					margin: 8upx 0 26upx;
+				}
+
+				.sku {
+					width: 180upx;
+					height: 50upx;
+					margin: 0 auto;
+					line-height: 50upx;
+					border: 2upx solid #E4E5E6;
+					background: url("../../static/images/origin/arrow-suk-select.png") no-repeat right center / 60upx 60upx;
+
+					.text {
+						font-size: 24upx;
+						color: #999;
+						padding-left: 20upx;
+						width: 126px;
+						text-overflow: ellipsis;
+						white-space: nowrap;
+					}
+				}
+			}
+		}
+	}
+
+	.swiper-dots {
+		display: flex;
+		justify-content: center;
+
+		.dot {
+			display: block;
+			width: 24upx;
+			height: 4upx;
+			background: #FFFFFF;
+			opacity: 0.5;
+			border-radius: 2upx;
+			margin: 0 20upx;
+
+			&.dot-active {
+				opacity: 1;
+			}
+		}
+	}
+}
+
+.btn-buy {
+	width: 688upx;
+	height: 84upx;
+	//border: 2upx solid rgba(0, 0, 0, 0);
+	background: linear-gradient(88deg, #C5AA7B 0%, #FFEBC4 100%);
+	font-size: 28upx;
+	color: #333;
+	line-height: 84upx;
+	margin: 30upx auto 0;
+	text-align: center;
+}
+
+.goosDetailshow-box {
+	.detailImg-box {
+		margin-top: 30rpx;
+		margin-left: 30rpx;
+		border-radius: 10rpx;
+		border-bottom: 1rpx solid #EDEDED;
+		padding-bottom: 20rpx;
+		width: 690rpx;
+
+		.detailImg {
+			width: 180rpx;
+			height: 180rpx;
+		}
+	}
+
+	.color-box {
+		padding: 30rpx 30rpx;
+		border-bottom: 1rpx solid #EDEDED;
+		width: 690rpx;
+
+		.skuStyle {
+			padding: 20rpx 0;
+		}
+
+		.skuStyle:nth-child(2) {
+			border-top: 1px solid #F3F4F5;
+		}
+
+		.colorName-box {
+			display: flex;
+			flex-wrap: wrap;
+			flex-direction: row;
+			justify-content: flex-start;
+			align-items: center;
+			margin-top: 30rpx;
+			margin-left: -30rpx;
+
+			.colorName-on {
+				background-color: #FFE5D0;
+				color: #C5AA7B;
+				margin-left: 30rpx;
+				padding: 10rpx 32rpx;
+				border-radius: 28rpx;
+				border: 1rpx solid #C5AA7B;
+				font-size: 26rpx;
+				text-align: center;
+				z-index: 1;
+			}
+
+			.colorName {
+				background-color: #F5F5F5;
+				margin-left: 30rpx;
+				padding: 10rpx 32rpx;
+				border-radius: 28rpx;
+				font-size: 26rpx;
+				z-index: 2;
+			}
+		}
+
+	}
+
+	.modelNum-box {
+		padding: 30rpx 30rpx;
+		border-bottom: 1rpx solid #EDEDED;
+		width: 690rpx;
+
+		.modelNumName-box {
+			display: flex;
+			flex-wrap: wrap;
+			flex-direction: row;
+			justify-content: flex-start;
+			align-items: center;
+			margin-top: 30rpx;
+			margin-left: -30rpx;
+
+			.modelNumName-on {
+				background-color: #FFE4D0;
+				color: #C5AA7B;
+				margin-left: 30rpx;
+				padding: 10rpx 32rpx;
+				border-radius: 28rpx;
+				border: 1rpx solid #C5AA7B;
+				font-size: 26rpx;
+				text-align: center;
+			}
+
+			.modelNumName {
+				background-color: #F5F5F5;
+				margin-left: 30rpx;
+				padding: 10rpx 32rpx;
+				border-radius: 28rpx;
+				font-size: 26rpx;
+			}
+		}
+	}
+
+	.goodsNum-box {
+		padding: 30rpx 30rpx;
+		width: 690rpx;
+		padding-bottom: 140rpx;
+
+		.goodsNumber {
+			text-align: center;
+			border: 1rpx solid #999999;
+			padding: 3rpx 20rpx;
+		}
+
+		.subtract {
+			border: 1rpx solid #999999;
+			padding: 3rpx 20rpx;
+			margin-right: -1rpx;
+		}
+
+		.add {
+			border: 1rpx solid #999999;
+			padding: 3rpx 20rpx;
+			margin-left: -1rpx;
+		}
+	}
+
+	.goosDetailbut-box {
+		.joinShopCartBut {
+			width: 343rpx;
+			height: 80rpx;
+			background-color: #FFC300;
+			color: #FFFEFE;
+			font-size: 28rpx;
+			line-height: 80rpx;
+			text-align: center;
+			margin-left: 30rpx;
+		}
+
+		.buyNowBut {
+			width: 343rpx;
+			height: 80rpx;
+			border-radius: 0 40rpx 40rpx 0;
+			background-color: #FF6F00;
+			color: #FFFEFE;
+			font-size: 28rpx;
+			line-height: 80rpx;
+			text-align: center;
+		}
+	}
+
+	.submitBtn {
+		width: 342rpx;
+		height: 100rpx;
+		line-height: 100rpx;
+		font-size: 28rpx;
+		border: 1px solid;
+		border-radius: 0;
+		color: #FFEBC4;
+		background: #333333;
+		margin: 20rpx 0;
+	}
+}
+</style>

+ 411 - 0
components/adWindow.vue

@@ -0,0 +1,411 @@
+<template>
+	<view v-if="visible">
+		<view
+			v-if="adInfo && adInfo.jumpType === 3 && couponList && couponList.length > 0" class="mask mask-coupon ad-coupons"
+			@touchmove.stop.prevent="moveHandle"
+		>
+			<view class="ad-box-warp">
+				<view class="ad-boxs">
+					<image class="img" :src="adInfo.popupImg"></image>
+					<view class="coupon-list">
+						<scroll-view :scroll-top="0" class="scrollBox" scroll-y="true">
+							<view v-for="(item, index) in couponList" :key="index" class="item">
+								<view class="leftBox borderBox">
+									<view class="boxTop"></view>
+									<view class="boxCent"></view>
+									<view class="boxBottom"></view>
+								</view>
+								<view class="centerBox">
+									<view class="money">
+										<text class="num" :class="[ item.discountMode === 1 ? 'num-minus' : 'num-discount' ]">
+											{{ item.reduceMoney }}
+										</text>
+										<text class="text">
+											满{{ item.fullMoney }}元可用
+										</text>
+									</view>
+									<view class="text">
+										<text>
+											{{ item.activityName }}
+										</text>
+									</view>
+								</view>
+								<view class="rightBox borderBox">
+									<view class="boxTop"></view>
+									<view class="boxCent"></view>
+									<view class="boxBottom"></view>
+								</view>
+							</view>
+						</scroll-view>
+					</view>
+					<WxSendCoupon v-if="couponList && couponList.length > 0" :coupon-list="couponList" @closeAd="close">
+						<view class="btn-receive">一键领取</view>
+					</WxSendCoupon>
+				</view>
+				<view class="close-btn">
+					<image :src="adInfo.closeImg" class="btn" mode="widthFix" @click="close()"></image>
+				</view>
+			</view>
+		</view>
+		<view v-else-if="adInfo && adInfo.jumpType !== 3" class="mask mask-coupon ad-link">
+			<view class="ad-box-warp">
+				<view class="ad-boxs" @click="goRoll()">
+					<image class="img" :src="adInfo.popupImg" mode="widthFix"></image>
+				</view>
+				<view class="close-btn">
+					<image :src="adInfo.closeImg" class="btn" mode="widthFix" @click="close()"></image>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import WxSendCoupon from './wx/wxSendCoupon'
+import { J_STORAGE_KEY } from '../config/constant'
+
+const NET = require('../utils/request')
+const API = require('../config/api')
+
+export default {
+	name: 'AdWindow',
+	components: {
+		WxSendCoupon
+	},
+	props: {
+		triggerCondition: {
+			type: Number,
+			default: 1
+		}
+	},
+	data() {
+		return {
+			visible: false,
+			adInfo: {},
+			jumpContent: {},
+			couponList: [],
+			isLogin: false,
+			buyerUserId: 0,
+			cParams: {}
+		}
+	},
+	methods: {
+		// 阻止滑动
+		moveHandle() {
+
+		},
+		// 获取广告信息
+		getAd() {
+			const res = uni.getStorageSync(J_STORAGE_KEY)
+			const token = res.token
+			setTimeout(() => {
+				this.buyerUserId = res.buyerUserId
+				this.isLogin = !!token
+				NET.request(API.GetAd, {
+					triggerCondition: this.triggerCondition
+				}, 'POST').then((res) => {
+					if (res.data) {
+						this.adInfo = res.data[0]
+						// console.log(this.adInfo)
+						if (this.adInfo && this.adInfo.jumpContent) {
+							this.jumpContent = JSON.parse(this.adInfo.jumpContent)
+						}
+						this.visible = true
+						if (this.adInfo && this.adInfo.jumpType === 3) {
+							this.getCoupons()
+						}
+					}
+				})
+			}, 500)
+		},
+		// 查询优惠券
+		getCoupons() {
+			if (this.isLogin) {
+				const _items = this.jumpContent.items
+				if (_items) {
+					NET.request(API.getCoupons, {
+						page: 1,
+						pageSize: 99,
+						ids: _items
+					}, 'GET').then((res) => {
+						if (res.data) {
+							this.couponList = res.data.list
+						}
+					})
+						.catch((res) => {
+
+						})
+				}
+			} else {
+				uni.showToast({
+					title: '登录之后领取更多优惠',
+					icon: 'none'
+				})
+				// uni.navigateTo({
+				// 	url: '/pages_category_page2/userModule/login'
+				// })
+			}
+		},
+		// 关闭弹窗
+		close() {
+			this.visible = false
+			var params = {}
+			if (this.isLogin) {
+				params.buyerUserId = this.buyerUserId
+			} else {
+				uni.getSystemInfo({
+					success(res) {
+						params.deviceId = res.deviceId
+					}
+				})
+			}
+			NET.request(API.adClose, params, 'POST').then((res) => {
+			})
+				.catch((res) => {
+				})
+		},
+		goRoll() {
+			this.visible = false
+			switch (this.adInfo.jumpType) {
+				case 1:
+					uni.navigateTo({
+						url: '/pages_category_page1/goodsModule/goodsDetails?shopId=' + this.jumpContent
+							.shopId + '&productId=' + this.jumpContent.id + '&skuId=' + this.jumpContent
+								.skuId
+					})
+					break
+				case 2:
+					const _id = this.jumpContent.id[this.jumpContent.id.length - 1]
+					uni.navigateTo({
+						url: `/pages_category_page1/goodsModule/goodsList?category3Id=${_id}`
+					})
+					break
+				case 4:
+					uni.navigateToMiniProgram({
+						appId: this.jumpContent.appId,
+						path: this.jumpContent.link,
+						success(res) {
+							// 打开成功
+						}
+					})
+				case 5:
+					uni.navigateTo({
+						path: this.jumpContent.link
+					})
+					break
+			}
+		}
+	}
+}
+</script>
+
+<style scoped
+       lang="scss"
+>
+.mask {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	z-index: 55;
+	background-color: rgba(0, 0, 0, 0.7);
+}
+
+.mask-coupon {
+	z-index: 9999;
+	/*background: rgba(39, 38, 39, .15);*/
+	display: flex;
+	justify-content: center;
+	align-items: center;
+
+	.ad-box-warp {
+		width: 100%;
+		position: relative;
+	}
+
+	flex-direction: column;
+
+	.ad-boxs {
+		position: relative;
+		width: 100%;
+		text-align: center;
+
+		.img {
+			width: 70%;
+		}
+	}
+
+	.btn-receive {
+		width: 446rpx;
+		height: 84rpx;
+		background: #EC6F43;
+		border-radius: 42rpx;
+		display: block;
+		text-align: center;
+		font-size: 28rpx;
+		line-height: 84rpx;
+		color: #fff;
+		position: absolute;
+		bottom: 32rpx;
+		left: 50%;
+		margin-left: -223rpx;
+	}
+
+	.close-btn {
+		position: absolute;
+		bottom: -70rpx;
+		left: 50%;
+		margin-left: -25rpx;
+
+		.btn {
+			width: 50rpx;
+			height: 50rpx;
+		}
+	}
+}
+
+.ad-coupons {
+	.ad-box-warp {
+		width: 510rpx;
+		position: relative;
+
+		.ad-boxs {
+			min-height: 700rpx;
+
+			.img {
+				position: absolute;
+				top: 0;
+				left: 0;
+				width: 100%;
+				height: 100%;
+			}
+		}
+	}
+
+	.coupon-list {
+		width: 446rpx;
+		height: 40%;
+		overflow: auto;
+		position: absolute;
+		top: 40%;
+		left: 50%;
+		margin-left: -223rpx;
+
+		.scrollBox {
+			height: 450upx;
+		}
+
+		.item {
+			width: 100%;
+			height: 140rpx;
+			margin-top: 15rpx;
+			border-radius: 8rpx;
+			display: flex;
+			position: relative;
+			align-items: center;
+			overflow: hidden;
+
+			&:first-child {
+				margin-top: 0px;
+			}
+
+			.borderBox {
+				width: 32rpx;
+				height: 140rpx;
+				overflow: hidden;
+
+				.boxTop {
+					width: 32rpx;
+					height: 54rpx;
+					background: #FFFFFF;
+				}
+
+				.boxCent {
+					height: 36rpx;
+					overflow: hidden;
+					position: relative;
+
+					&:after {
+						content: '';
+						width: 32rpx;
+						height: 32rpx;
+						border-radius: 50%;
+						display: block;
+						position: absolute;
+						top: 50%;
+						margin-top: -47rpx;
+						left: -15rpx;
+						border: 32rpx solid #FFFFFF;
+					}
+				}
+
+				.boxBottom {
+					width: 32rpx;
+					height: 54rpx;
+					background: #FFFFFF;
+				}
+			}
+
+			.leftBox {
+				.boxCent {
+					&:after {
+						left: -50rpx;
+					}
+				}
+			}
+
+			.rightBox {}
+
+			.centerBox {
+				display: flex;
+				align-items: center;
+				height: 140rpx;
+				background: #FFFFFF;
+				flex: 1;
+			}
+
+			.money {
+				width: 190rpx;
+				text-align: center;
+
+				.num {
+					font-size: 48rpx;
+					color: #EC6F43;
+					display: block;
+
+					&.num-minus::before {
+						content: '¥';
+						font-size: 36rpx;
+					}
+
+					&.num-discount::after {
+						content: '折';
+						font-size: 36rpx;
+					}
+				}
+
+				.text {
+					font-size: 24rpx;
+					color: #999;
+				}
+			}
+
+			.text {
+				flex: 1;
+				padding-right: 16rpx;
+				width: 0;
+
+				text {
+					font-size: 32rpx;
+					color: #333;
+					display: block;
+					overflow: hidden;
+					text-overflow: ellipsis;
+					white-space: nowrap;
+				}
+			}
+		}
+	}
+}
+</style>

+ 85 - 0
components/basics/categoryList.vue

@@ -0,0 +1,85 @@
+<template>
+  <view class="tabs-nav-warp">
+    <scroll-view class="tabs-nav" scroll-x="true">
+      <view class="ul">
+        <view class="li" :class="{'on':activeTab===0}" @click="tabChange(0)">首页</view>
+        <view class="li" :class="{'on':activeTab===index+1}" v-for="(item,index) in categoryList" :key="index" @click="tabChange(index+1,item.classifyId)">
+          {{item.classifyName}}
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+const NET = require('@/utils/request')
+const API = require('@/config/api')
+export default {
+  name: "categoryList",
+  data () {
+    return {
+      activeTab: 0,
+      categoryList: []
+    }
+  },
+  mounted () {
+    this.getCategoryData();
+  },
+  methods:{
+    tabChange (index, id) {
+      this.activeTab = index
+      this.$emit('tabChange', index, id)
+    },
+    getCategoryData(){
+      uni.showLoading({
+        title:'加载中...'
+      })
+      NET.request(API.FindCategoryListByDepth, {
+      }, 'GET').then(res => {
+        this.categoryList = res.data
+        uni.hideLoading()
+      }).catch(res => {
+        uni.hideLoading()
+      })
+    }
+  }
+
+}
+</script>
+
+<style lang="scss" scoped>
+.tabs-nav-warp{
+  //margin-top: 20rpx;
+  padding:0 30rpx;
+  .tabs-nav{
+    .ul{
+      display: flex;
+      .li{
+        flex: 1 0 auto;
+        margin-left: 36rpx;
+        font-size: 30rpx;
+        color: #999999;
+        position: relative;
+        padding: 18rpx 0;
+        box-sizing: border-box;
+        &:first-child{
+          margin-left: 0;
+        }
+        &.on{
+          &:after{
+            content: '';
+            width: 100%;
+            height: 4rpx;
+            background: #C5AA7B;
+            position: absolute;
+            left: 0;
+            bottom: 0;
+          }
+          font-weight:bold;
+        }
+      }
+    }
+  }
+}
+
+</style>

+ 332 - 0
components/basics/categoryShow.vue

@@ -0,0 +1,332 @@
+<template>
+	<view class="goodRecommend">
+		<ProductSkeleton :loading="loading" :is-first="isFirst" />
+		<template v-if="!isFirst">
+			<view class="product-list">
+				<view class="product-list-box">
+					<view v-for="(item, index) in productList" :key="index" class="product-list-item-warp">
+						<view class="product-list-item">
+							<view class="product-list-img" @click="handleJumpProductDetail(item)">
+								<img v-show="item.image" class="img" :src="item.image">
+							</view>
+							<view class="product-list-info u-skeleton-fillet">
+								<view class="product-name">{{ item.productName }}</view>
+								<view class="flex">
+									<view class="shop-box" @click.stop="handleJumpStore(item)">
+										<view class="shop-name" @click="handleJumpProductDetail(item)">
+											{{ item.shopName }}
+										</view>
+										<view class="shop-logo">
+											<img :src="item.shopLogo">
+										</view>
+									</view>
+									<view class="buy-count">{{ item.users ? item.users : 0 }}人付款</view>
+								</view>
+								<div class="price-warp">
+									<image class="iconImg" :src="getPriceActivity(item.activityType)" />
+									<div class="price">
+										¥ {{ item.price }}
+									</div>
+									<div class="original-price">
+										¥ {{ item.originalPrice }}
+									</div>
+								</div>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 空状态 -->
+			<Empty
+				background="#fff" :show="!loading && productList.length < 0"
+				src="../../static/images/origin/bgnull.png"
+			>
+				这里空空如也
+			</Empty>
+
+			<!-- 底部提示 -->
+			<ListBottomTips
+				v-show="loading || total !== 0 && total === productList.length" :loading="loading"
+				:type="productList.length < total ? 1 : 0"
+			/>
+		</template>
+	</view>
+</template>
+
+<script>
+import Empty from '@/components/Empty'
+import ProductSkeleton from './components/ProductSkeleton'
+import ListBottomTips from '@/components/ListBottomTips'
+
+const NET = require('@/utils/request')
+const API = require('@/config/api')
+export default {
+	name: 'CategoryShow',
+	components: {
+		Empty,
+		ListBottomTips,
+		ProductSkeleton
+	},
+	props: {
+		classifyId: {
+			type: Number,
+			default: 0
+		}
+	},
+	data() {
+		return {
+			page: 1,
+			pageSize: 10,
+			total: 0,
+			productList: [],
+			isFirst: true,
+			loading: true // 是否正在请求
+		}
+	},
+	computed: {
+		// 获取活动小图标
+		getPriceActivity() {
+			return (activityType) => {
+				switch (activityType) {
+					case 1:
+						return require('../canvasShow/static/images/groupBuyIcon.png')
+					case 2:
+						return require('../canvasShow/static/images/spikeIcon.png')
+					case 3:
+						return require('../canvasShow/static/images/discountListIcon.png')
+					case 4:
+						return require('../canvasShow/static/images/spikeIcon.png')
+					case 5:
+						return require('../canvasShow/static/images/discountListIcon.png')
+					case 8:
+						return require('../canvasShow/static/images/memberCenterIcon.png')
+				}
+			}
+		}
+	},
+	watch: {
+		'classifyId': {
+			handler() {
+				this.handleResetList()
+				this.handleResetPage()
+				this.handleGetProductData()
+			},
+			deep: true
+		}
+	},
+	mounted() {
+		this.handleResetList()
+		this.handleResetPage()
+		this.handleGetProductData()
+	},
+	methods: {
+		// 重设苏族
+		handleResetList() {
+			uni.pageScrollTo({
+				scrollTop: 0,
+				duration: 0
+			})
+			this.isFirst = true
+			this.productList = []
+		},
+		handleResetPage() {
+			this.total = 0
+			this.page = 1
+		},
+		// 获取商品数据
+		handleGetProductData() {
+			if (this.total !== 0 && this.productList.length >= this.total) {
+				return
+			}
+			this.loading = true
+			NET.request(API.getProductsV2, {
+				classifyId: this.classifyId,
+				page: this.page,
+				pageSize: this.pageSize
+			}, 'GET').then((res) => {
+				this.productList = [...this.productList, ...res.data.list]
+				this.total = res.data.total
+			})
+				.catch((err) => {
+					throw Error(err)
+				})
+				.finally(() => {
+					this.loading = false
+					this.isFirst = false
+				})
+		},
+		// 跳转到店铺主页
+		handleJumpStore(item) {
+			uni.navigateTo({
+				url: `/pages_category_page1/store/index?storeId=${item.shopId}`
+			})
+		},
+		// 跳转到商品详情
+		handleJumpProductDetail(item) {
+			uni.navigateTo({
+				url: '/pages_category_page1/goodsModule/goodsDetails?shopId=' + item.shopId + '&productId=' + item.productId + '&skuId=' + item
+					.skuId
+			})
+		}
+	}
+}
+</script>
+
+<style
+    lang="scss"
+    scoped
+>
+.goodRecommend {
+	padding-top: 20rpx;
+
+	.product-list {
+		position: relative;
+		padding: 0 13upx;
+		width: 100%;
+
+		//min-height: 60vh;
+		&-box {
+			display: flex;
+			flex-wrap: wrap;
+			flex-direction: row;
+
+			&.swiper {
+				height: 620upx;
+			}
+		}
+
+		&.product-swiper .product-list-box {
+			padding-left: 0;
+		}
+
+		&-item-warp {
+			margin: 0 0 20upx 0;
+		}
+
+		&-item {
+			width: 348upx;
+			padding: 0 7upx;
+			box-sizing: content-box;
+		}
+
+		&-img {
+			width: 348upx;
+			height: 348upx;
+			background-color: #d0d0d0;
+			border-radius: 10upx 10upx 0 0;
+
+			.img {
+				width: 100%;
+				height: 100%;
+				object-fit: contain;
+			}
+		}
+
+		&-info {
+			background-color: #FFFFFF;
+			//box-shadow: 0px 0px 15px 0px rgba(52, 52, 52, 0.15);
+			border-radius: 0 0 10upx 10upx;
+			padding: 20upx;
+
+			label {
+				font-weight: normal;
+			}
+
+			.product-name {
+				font-size: 28upx;
+				color: #333;
+				display: block;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+				margin-bottom: 18upx;
+				line-height: 40upx;
+			}
+
+			.flex {
+				display: flex;
+				align-items: center;
+			}
+
+			.shop-box {
+				background-color: #333333;
+				border-radius: 0upx 20upx 20upx 0upx;
+				line-height: 40upx;
+				display: flex;
+				align-items: center;
+				height: 40upx;
+				margin-right: 10upx;
+				float: left;
+
+				.shop-name {
+					font-size: 20upx;
+					color: #FFEBC4;
+					padding: 0 8upx 0 12upx;
+					line-height: 40upx;
+					display: inline-block;
+					float: left;
+					max-width: 170rpx;
+					white-space: nowrap;
+					overflow: hidden;
+					text-overflow: ellipsis;
+				}
+
+				.shop-logo {
+					border: 2upx solid #707070;
+					border-radius: 50%;
+					overflow: hidden;
+					float: right;
+
+					img {
+						width: 34upx;
+						height: 34upx;
+						display: block;
+					}
+				}
+			}
+
+			.buy-count {
+				color: #C5AA7B;
+				font-size: 20upx;
+				border: 2upx solid #E4E5E6;
+				line-height: 36upx;
+				padding: 0 5upx;
+			}
+
+			.price-warp {
+				display: flex;
+				align-items: baseline;
+				line-height: 56upx;
+
+				.iconImg {
+					width: 58rpx;
+					height: 36rpx;
+					margin-right: 10rpx;
+				}
+
+				.price {
+					color: #C83732;
+					font-size: 40upx;
+					margin-right: 20upx;
+				}
+
+				.original-price {
+					font-size: 24upx;
+					color: #ccc;
+					text-decoration: line-through;
+				}
+			}
+		}
+	}
+
+	.emptyCart-box {
+		margin-top: 70rpx;
+
+		.emptyCart-img {
+			width: 216rpx;
+			height: 156rpx;
+		}
+	}
+}
+</style>

+ 80 - 0
components/basics/components/ProductSkeleton.vue

@@ -0,0 +1,80 @@
+<template>
+	<view v-if="isFirst">
+		<Skeleton :loading="isFirst && loading" :animation="true" bg-color="#FFF" />
+		<view class="skeleton_content u-skeleton">
+			<view class="empty_row"></view>
+			<view class="product_box">
+				<view v-for="item in 40" class="product_item ">
+					<view class="image u-skeleton-fillet"></view>
+					<view class="text_box ">
+						<view v-for="item in 2" class="text u-skeleton-fillet"></view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import Skeleton from '@/components/Skeleton'
+export default {
+	name: 'ProductSkeleton',
+	components: { Skeleton },
+	props: {
+		isFirst: {
+			type: Boolean,
+			default: () => true
+		},
+		loading: {
+			type: Boolean,
+			default: () => true
+		}
+	},
+	data() {
+		return {}
+	},
+	methods: {}
+}
+</script>
+
+<style
+    lang="scss"
+    scoped
+>
+.skeleton_content {
+	width: 100%;
+	height: calc(100vh - 50px);
+	padding: 30rpx 30rpx;
+	box-sizing: border-box;
+
+	.product_box {
+		width: 100%;
+		display: flex;
+		justify-content: space-between;
+		flex-wrap: wrap;
+
+		.product_item {
+			margin: 10rpx 0;
+			width: 49%;
+			height: 500rpx;
+
+			//padding: 30rpx 30rpx;
+			//box-sizing: border-box;
+			.image {
+				width: 100%;
+				height: 70%;
+			}
+
+			.text_box {
+				padding: 0 20rpx;
+
+				.text {
+					margin-top: 15rpx;
+					width: 100%;
+					height: 60rpx;
+				}
+			}
+		}
+	}
+}
+</style>

+ 24 - 0
components/canvasShow/basics/assistDiv.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="div" :style="{backgroundColor:componentContent.bgColor,height:componentContent.height + 'rpx'}"></div>
+</template>
+
+<script>
+  export default {
+    name: 'assistDiv',
+    props: {
+      terminal: {
+        type: Number,
+        default: 4
+      },
+      componentContent: {
+        type: Object
+      }
+    }
+  }
+</script>
+
+<style scoped>
+  .div{
+    width: 100%;
+  }
+</style>

+ 121 - 0
components/canvasShow/basics/banner.vue

@@ -0,0 +1,121 @@
+<template>
+	<div class="banner" :class="'terminal' + terminal">
+		<swiper
+			class="swiper" :circular="true" :indicator-dots="false" :autoplay="true"
+			:style="{ 'height': bannerHeight + 'rpx' }" @change="swiperChange"
+		>
+			<swiper-item
+				v-for="(item, index) in bannerList" :key="index" class="banner-item"
+				:style="{ backgroundImage: 'url(' + item.bannerUrl + ')' }" @click="jumpLink(item.linkObj)"
+			>
+				<!--        <div class="a-link" @click="jumpLink(item.linkObj)"><img class="img" :src="item.bannerUrl" v-show="item.bannerUrl" mode="widthFix"></div> -->
+			</swiper-item>
+		</swiper>
+		<view v-if="bannerList && bannerList.length > 1" class="swiper-dots">
+			<text
+				v-for="(dot, index) in bannerList.length" :key="index" class="dot"
+				:class="index === swiperCurrent && 'dot-active'"
+			></text>
+		</view>
+	</div>
+</template>
+
+<script>
+import { funMixin } from '../config/mixin'
+export default {
+	name: 'ShopBanner',
+	mixins: [ funMixin ],
+	props: {
+		terminal: {
+			type: Number,
+			default: 4
+		},
+		componentContent: {
+			type: Object
+		}
+	},
+	data() {
+		return {
+			bannerHeight: 0,
+			swiperCurrent: 0
+		}
+	},
+	computed: {
+		bannerList() {
+			return this.componentContent.bannerData.filter(function (item) {
+				return item.bannerUrl
+			})
+		}
+	},
+	mounted() {
+		this.bannerHeight = this.componentContent.height
+		this.$forceUpdate() // 刷新轮播图
+	},
+	methods: {
+		swiperChange(e) {
+			this.swiperCurrent = e.detail.current
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.banner {
+	position: relative;
+
+	.banner-item {
+		width: 100%;
+		background-repeat: no-repeat;
+		background-position: center;
+		background-size: auto 100%;
+
+		img {
+			display: none;
+		}
+	}
+
+	&.terminal4 {
+		::v-deep .el-carousel {
+			height: 100%;
+
+			.el-carousel__container {
+				height: 100%;
+			}
+		}
+
+		.banner-item {
+			background-repeat: no-repeat;
+			background-position: center;
+			background-size: auto 100%;
+
+			img {
+				display: none;
+			}
+		}
+	}
+
+	.swiper-dots {
+		display: flex;
+		position: absolute;
+		left: 50%;
+		transform: translateX(-50%);
+		bottom: 20upx;
+		z-index: 200;
+
+		.dot {
+			width: 12upx;
+			height: 12upx;
+			background: #FFFFFF;
+			border-radius: 6upx;
+			opacity: 0.2;
+			margin: 0 10upx;
+		}
+
+		.dot-active {
+			opacity: 1;
+			width: 24upx;
+		}
+	}
+
+}
+</style>

+ 131 - 0
components/canvasShow/basics/brandList.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="brand-list warp" :class="'terminal' + terminal">
+    <h2 class="hom-title" :style="{textAlign:componentContent.textAlign}">{{componentContent.title}}</h2>
+    <div class="content-warp">
+      <div class="ul clearfix">
+        <div class="li" v-for="(item, index) in componentContent.imgList" :key="index">
+          <a class="item a-link" @click="jumpLink(item.linkObj)">
+            <div class="imgBox">
+              <img :src="item.imgData" v-show="item.imgData" :alt="item.title">
+            </div>
+            <h4 class="h4">{{item.title}}</h4>
+          </a>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {funMixin} from '../config/mixin'
+export default {
+  name: 'brandList',
+  mixins: [funMixin],
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    componentContent: {
+      type: Object
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+  .brand-list{
+    color: #fff;
+    padding: 20upx 0;
+    .hom-title{
+      font-size: 22upx;
+      color: #333;
+      line-height: 1em;
+      margin-bottom: 23upx;
+      font-weight: bold;
+    }
+    .content-warp{
+      display: flex;
+    }
+    .first{
+      width: 24%;
+      padding-top: 10upx;
+      .item{
+        background-color: #7A8594;
+        height: 336upx;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        .h3{
+          font-size: 30upx;
+          line-height: 46upx;
+          margin-bottom: 10upx;
+        }
+      }
+    }
+    .ul{
+      width: 100%;
+      .li{
+        width: 25%;
+        float: left;
+        padding: 10upx 0 0 10upx;
+        box-sizing: border-box;
+        .item{
+          height: auto;
+          display: flex;
+          flex-direction: column;
+          justify-content: center;
+          .imgBox {
+            padding-bottom: 80%;
+            background-color: #cacaca;
+            position: relative;
+          }
+          .h4{
+            font-size: 18upx;
+            color: #333333;
+            text-align: center;
+            height: 40upx;
+            line-height: 40upx;
+          }
+          .p{
+            font-size: 12upx;
+            margin: 7upx 0 12upx;
+          }
+          img {
+            max-width: 100%;
+            height: 100%;
+            max-height: 100%;
+            position: absolute;
+            margin: auto;
+            top: 0;
+            right: 0;
+            bottom: 0;
+            left: 0;
+          }
+        }
+      }
+    }
+  }
+  .terminal1,.terminal2,.terminal3{
+    &.brand-list{
+      width: 710upx;
+      margin: 0 auto;
+      .content-warp{
+        display: block;
+      }
+      .first{
+        width: 100%;
+      }
+      .ul{
+        width: auto;
+        margin-left: -15upx;
+        .li{
+          width: 50%;
+          padding: 15upx 0 0 15upx;
+          .item {
+            padding-left: 0;
+          }
+        }
+      }
+    }
+  }
+</style>

+ 109 - 0
components/canvasShow/basics/categoryList.vue

@@ -0,0 +1,109 @@
+<template>
+  <div class="category-list warp" :class="'terminal' + terminal">
+    <h2 class="hom-title" :style="{textAlign:componentContent.textAlign}">{{componentContent.title}}</h2>
+    <div class="content-warp">
+      <div class="ul clearfix" :class="{number5: componentContent.categoryData.length === 5}">
+        <div class="li" v-for="(item) of componentContent.categoryData" :key="item.id">
+          <a class="item a-link" @click="jumpCategory(item)">
+            <div class="imgBox">
+              <img ref="getHeight" :src="item.img" v-show="item.img" :alt="item.name">
+            </div>
+          </a>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {funMixin} from '../config/mixin'
+export default {
+  name: 'categoryList',
+  mixins: [funMixin],
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    componentContent: {
+      type: Object
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+  .category-list{
+    padding: 20upx 0;
+    .hom-title{
+      font-size: 22upx;
+      color: #333;
+      line-height: 1em;
+      margin-bottom: 23upx;
+      font-weight: bold;
+      text-align: center;
+    }
+    .content-warp{
+      display: flex;
+      .ul{
+        width: 100%;
+        display: flex;
+        flex-wrap: wrap;
+        .li{
+          flex: 1;
+          padding: 10upx 0 0 10upx;
+          box-sizing: border-box;
+          .item{
+            height: auto;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            .imgBox {
+              padding-bottom: 80%;
+              background-color: #cacaca;
+              position: relative;
+            }
+            img {
+              max-width: 100%;
+              height: 100%;
+              max-height: 100%;
+              position: absolute;
+              margin: auto;
+              top: 0;
+              right: 0;
+              bottom: 0;
+              left: 0;
+            }
+          }
+        }
+      }
+      .number5 {
+        display: block;
+        .li {
+          width: 25%;
+          float: left;
+        }
+        .li:nth-child(1) {
+          width: 50%;
+        }
+      }
+    }
+  }
+  @media screen and (max-width: 768px) {
+    .category-list .content-warp .ul .li{
+      flex: 0 0 50%;
+    }
+  }
+  .terminal1,.terminal2,.terminal3{
+    &.category-list .content-warp{
+      display: block;
+      .ul{
+        margin: -15upx 0 0 -15upx;
+        width: auto;
+        .li{
+          flex: 0 0 50%;
+          padding: 15upx 0 0 15upx;
+        }
+      }
+    }
+  }
+</style>

+ 113 - 0
components/canvasShow/basics/coupon/app/index.vue

@@ -0,0 +1,113 @@
+<template>
+  <div class="coupon">
+    <div class="coupon-list">
+      <div class="coupon-item" v-for="(item,index) in couponsData" :key="index" :class="item.state && item.state !== 3 && 'isReceive'">
+        <!-- #ifdef MP-WEIXIN -->
+        <img class="coupon-item-bg" src="../../../static/images/coupon/bg-coupon.png" v-if="item.state && item.state === 3" mode="widthFix">
+        <img class="coupon-item-bg" src="../../../static/images/coupon/bg-coupon2.png" v-else mode="widthFix">
+        <!-- #endif -->
+        <!-- #ifdef H5 || APP-PLUS -->
+        <image class="coupon-item-bg" src="../../../static/images/coupon/bg-coupon.png" v-if="item.state && item.state === 3" mode="widthFix">
+        <image class="coupon-item-bg" src="../../../static/images/coupon/bg-coupon2.png" v-else mode="widthFix">
+        <!-- #endif -->
+        <div class="coupon-item-cont">
+        <div class="coupon-item-content">
+          <div class="coupon-item-price">
+            <div class="span" v-if="item.couponType !== 2">¥</div>
+            <div class="b" v-if="typeId !== 1">{{item.couponContent}}</div>
+            <div class="b" v-else>{{item.reduceMoney}}</div>
+            <div class="b" v-if="item.couponType == 2">折券</div>
+          </div>
+          <div class="coupon-item-date">{{item.startTime.split(' ')[0].replace(/-/g, '.')}}-{{item.endTime.split(' ')[0].replace(/-/g, '.')}}</div>
+          <div class="coupon-item-text">{{item.content}}</div>
+        </div>
+        <button v-if="item.state === 0" class="coupon-item-btn">已领取</button>
+        <button v-else-if="item.state === 1" class="coupon-item-btn">已使用</button>
+        <button v-else-if="item.state === 2" class="coupon-item-btn">已过期</button>
+        <button v-else @click="receiveCoupon(item)" class="coupon-item-btn">立即领取</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+export default {
+  mixins: [commonMixin]
+}
+</script>
+
+<style lang="scss" scoped>
+.coupon {
+  padding: 25upx;
+  &-list{
+    display: flex;
+    flex-wrap: wrap;
+  }
+  &-item{
+    width: 335upx;
+    height: 292upx;
+    margin-left: 28upx;
+    text-align: center;
+    position: relative;
+    margin-bottom: 10upx;
+    &-bg{
+      width: 100%;
+      height: 100%;
+    }
+    &-cont{
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      left: 0;
+      top: 0;
+    }
+    &:nth-child(2n+1){
+      margin-left: 0;
+    }
+    &-content{
+      text-align: center;
+    }
+    &-price{
+      color: #C5AA7B;
+      margin: 26upx 0 10upx;
+      .span{
+        display: inline;
+        font-size: 36upx;
+        line-height: 68upx;
+      }
+      .b{
+        display: inline;
+        font-size: 50upx;
+        line-height: 68upx;
+      }
+    }
+    &-date,&-text{
+      font-size: 20upx;
+      line-height: 28upx;
+      color: #999999;
+    }
+    &-date{
+      margin-bottom: 8upx;
+    }
+    &-btn{
+      display: inline-block;
+      margin: 60upx auto 0;
+      height: 48upx;
+      line-height: 48upx;
+      background-color: #C5AA7B;
+      color: #fff;
+      font-size: 14upx
+    }
+    &.isReceive{
+      &-price{
+        color: #999;
+      }
+      &-btn{
+        background-color: #ccc;
+      }
+    }
+  }
+}
+</style>

+ 111 - 0
components/canvasShow/basics/coupon/mixin.js

@@ -0,0 +1,111 @@
+import api from '../../config/api'
+import NET from '../../../../utils/request'
+import { funMixin } from '../../config/mixin'
+import { J_STORAGE_KEY } from '../../../../config/constant'
+
+export const commonMixin = {
+	name: 'textComponent',
+	mixins: [ funMixin ],
+	data() {
+		return {
+			couponsData: []
+		}
+	},
+	props: {
+		terminal: {
+			type: Number,
+			default: 4
+		},
+		typeId: {
+			type: Number,
+			default: 1
+		},
+		shopId: {
+			type: Number,
+			default: 0
+		},
+		componentContent: {
+			type: Object
+		}
+	},
+	watch: {
+		'componentContent': {
+			handler(newVal, oldVal) {
+				this.getData()
+			},
+			deep: true
+		}
+	},
+	created() {
+		this.getData()
+	},
+	methods: {
+		getData() {
+			const _ = this
+			if (_.componentContent.selectedCoupon && _.componentContent.selectedCoupon.length > 0) {
+				let _url = ''
+				if (_.typeId === 1) {
+					_url = `${api.getCoupons}?page=1&pageSize=99&ids=${_.componentContent.selectedCoupon}`
+				} else if (_.typeId === 3) {
+					_url = `${api.getShopCoupons}?page=1&pageSize=99&shopId=${_.shopId}&ids=${_.componentContent.selectedCoupon}`
+				}
+				const params = {
+					method: 'GET',
+					url: _url
+				}
+				this.sendReq(params, (res) => {
+					_.couponsData = res.data.list
+					if (_.typeId === 1) {
+						_.couponsData.forEach((item) => {
+							item.couponName = item.activityName
+							item.effectiveStart = item.activityStartTime
+							item.effectiveEnd = item.activityEndTime
+						})
+					}
+					if (JSON.stringify(_.componentContent.couponList) !== JSON.stringify(_.couponsData)) {
+						_.componentContent.couponList = _.couponsData
+					}
+				})
+			} else {
+				_.couponsData = []
+			}
+		},
+		// 领取优惠券
+		receiveCoupon(item) {
+			const res = uni.getStorageSync(J_STORAGE_KEY)
+			const token = res.token
+			if (token) {
+				var paramsData = {}
+				if (this.typeId === 1) {
+					paramsData.couponId = item.couponId
+				} else if (this.typeId === 3) {
+					paramsData.shopCouponId = item.shopCouponId
+					paramsData.shopId = this.shopId
+				}
+				NET.request(api.takeCoupon, paramsData, 'POST').then((res) => {
+					this.getData()
+					uni.showToast({
+						title: '领取成功',
+						icon: 'success'
+					})
+				})
+					.catch((res) => {
+						if (res.data.code !== '200') {
+							uni.showToast({
+								title: res.data.message,
+								icon: 'none'
+							})
+						}
+					})
+			} else {
+				uni.showToast({
+					title: '请先登录',
+					icon: 'none'
+				})
+				uni.navigateTo({
+					url: '/pages_category_page2/userModule/login'
+				})
+			}
+		}
+	}
+}

+ 375 - 0
components/canvasShow/basics/coupon/pc/index.vue

@@ -0,0 +1,375 @@
+<template>
+  <div class="couponBox warp" :class="['terminal' + terminal,'arrange'+(componentContent.arrangeActiveIndex+1),'color'+(componentContent.colorActiveIndex+1)]">
+    <div class="couponListBox" v-if="componentContent.selectedCoupon">
+      <div class="listItemBox" v-for="(item,index) in couponsData" :key="index" :class="item.state && item.state !== 3 && 'isReceive'">
+        <div class="listItemBoxInner">
+          <div class="itemInfo">
+            <i class="flag" :class="'flag'+item.couponType"></i>
+            <div class="amount">
+              <b v-if="item.couponType !== 2">¥</b>
+              <span v-if="typeId !== 1">
+                {{item.couponContent}}
+                </span>
+              <span v-else>
+                {{item.reduceMoney}}
+                </span>
+              <b v-if="item.couponType == 2">折</b>
+            </div>
+            <div class="couponInfo">
+              <p>{{item.content}}</p>
+            </div>
+          </div>
+          <!--            <div class="itemInfo" v-else>-->
+          <!--              <div class="amount">-->
+          <!--                <b>¥</b><span>{{item.reduceMoney}}<i>满减券</i></span>-->
+          <!--              </div>-->
+          <!--              <div class="couponInfo">-->
+          <!--                <p>{{item.content}}</p>-->
+          <!--              </div>-->
+          <!--            </div>-->
+          <div v-if="item.state === 0" class="receiveBtn">
+            <span>己领取</span>
+          </div>
+          <div v-else-if="item.state === 1" class="receiveBtn">
+            <span>已使用</span>
+          </div>
+          <div v-else-if="item.state === 2" class="receiveBtn">
+            <span>已过期</span>
+          </div>
+          <div v-else class="receiveBtn" @click="receiveCoupon(item)">
+            <span>立即领取</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+export default {
+  mixins: [commonMixin]
+}
+</script>
+
+<style lang="scss" scoped>
+.couponBox {
+  min-height: 177px;
+  margin: 0 auto;
+  padding: 20px 0;
+  .couponListBox {
+    flex-wrap: wrap;
+    /**默认**/
+    .listItemBox {
+      //background-image:url('../../../static/images/coupon/border_L1.png'), url('../../../static/images/coupon/border_R1.png');
+      //background-repeat: no-repeat, no-repeat;
+      //background-position: left top, right top;
+      box-sizing: border-box;
+      margin-bottom: 20px;
+      .listItemBoxInner {
+        width: 100%;
+        height: 150px;
+        display: flex;
+        background-color: #FAFAFA;
+        align-items: center;
+        justify-content: space-between;
+        position: relative;
+        .flag{
+          display: block;
+          width: 71px;
+          height: 71px;
+          background-repeat: no-repeat;
+          position: absolute;
+          top: 0;
+          left: 0;
+          &.flag1{
+            background-image: url("../../../static/images/coupon/flag-coupon.png");
+          }
+          &.flag2{
+            background-image: url("../../../static/images/coupon/flag-coupon2.png");
+          }
+        }
+        .itemInfo {
+          flex: 1;
+        }
+        .amount {
+          max-width: 90%;
+          margin: 0 auto;
+          display: flex;
+          align-items: baseline;
+          justify-content: center;
+          line-height: 60px;
+          b {
+            font-size: 30px;
+          }
+          span {
+            font-size: 60px;
+            font-weight: bold;
+          }
+          i {
+            font-style: normal;
+            font-size: 18px;
+            margin-left: 5px;
+          }
+        }
+        .couponInfo {
+          text-align: center;
+          p {
+            display: inline-block;
+            padding: 0 42px;
+            text-align: center;
+            font-size: 18px;
+            line-height: 40px;
+            color: #C83732;
+            background: #F5E5E5;
+          }
+        }
+      }
+      .receiveBtn {
+        width: 72px;
+        margin-right: 5px;
+        background: #C5AA7B;
+        height: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+        position: relative;
+        &:before,&:after{
+          content: '';
+          display: block;
+          width: 25px;
+          height: 25px;
+          background-color: #fff;
+          position: absolute;
+          left: -12.5px;
+          border-radius: 50%;
+        }
+        &:before{
+          top: -12.5px;
+        }
+        &:after{
+          bottom: -12.5px;
+        }
+        span {
+          color: #FFFFFF !important;
+          writing-mode: vertical-lr;
+          font-size: 19px;
+        }
+      }
+      &.isReceive {
+        //background-image:url('../../../static/images/coupon/border_L4.png'), url('../../../static/images/coupon/border_R4.png');
+        .listItemBoxInner {
+          .flag{
+            &.flag1{
+              background-image: url("../../../static/images/coupon/flag-coupon-r.png");
+            }
+            &.flag2{
+              background-image: url("../../../static/images/coupon/flag-coupon2-r.png");
+            }
+          }
+          .itemInfo {
+            color: #999;
+            .couponInfo {
+              p {
+                color: #999;
+                background: #F1F1F1;
+              }
+            }
+          }
+          .receiveBtn {
+            cursor: auto;
+            background: #999;
+          }
+        }
+      }
+    }
+  }
+
+  @mixin cardColor($bgColor: #FF3737,$fontColor: #fff) {
+    .couponListBox {
+      .listItemBox {
+        .listItemBoxInner{
+          background: $bgColor;
+        }
+        .itemInfo {
+          .amount {
+            b {
+              color: #EC4B42;
+            }
+            span {
+              color: #EC4B42;
+              i {
+                color: #EC4B42;
+              }
+            }
+          }
+          .couponInfo {
+            color:#EC4B42;
+          }
+        }
+        .receiveBtn {
+          span {
+            color: #EC4B42;
+          }
+        }
+        &.cardStyle3{
+          .itemInfo {
+            .amount {
+              span {
+                color: $bgColor;
+                i {
+                  color: $bgColor;
+                }
+              }
+            }
+            .couponInfo {
+              color:$bgColor;
+            }
+          }
+        }
+        &.cardStyle4{
+          border: 2px solid $bgColor;
+          padding: 5px;
+          .listItemBoxInner{
+            padding: 20px 15px;
+            height: 85px;
+            border: 1px solid $bgColor;
+          }
+          .itemInfo {
+            .amount {
+              span {
+                color: $bgColor;
+                i {
+                  color: $bgColor;
+                }
+              }
+            }
+            .couponInfo {
+              color:$bgColor;
+            }
+          }
+          .receiveBtn {
+            border-left: 1px $bgColor dashed;
+            span {
+              color: $bgColor;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  &.arrange1{
+
+  }
+  &.arrange2{
+    max-width: 100%;
+    .couponListBox{
+      display: flex;
+      flex-wrap: wrap;
+      justify-content: space-between;
+      .listItemBox{
+        width: 48%;
+      }
+    }
+  }
+  &.arrange3{
+    max-width: 100%;
+    .couponListBox{
+      display: flex;
+      flex-wrap: wrap;
+      justify-content: space-between;
+      .listItemBox{
+        width: 32%;
+      }
+    }
+  }
+  &.arrange4{
+    max-width: 100%;
+    .couponListBox{
+      display: flex;
+      overflow: hidden;
+      .listItemBox{
+        width: 268px;
+        flex: 0 0 268px;
+        margin:0 25px 25px 0;
+        &:nth-child(4n){
+          margin-right: 0;
+        }
+      }
+    }
+  }
+  //&.color1{
+  //  .listItemBox {
+  //    background-image:url('../../../static/images/coupon/border_L1.png'), url('../../../static/images/coupon/border_R1.png');
+  //    .listItemBoxInner {
+  //      border-top: 1px solid #EC4B42;
+  //      border-bottom: 1px solid #EC4B42;
+  //      .itemInfo {
+  //        color: #EC4B42;
+  //        .amount {
+  //          border-bottom: 1px solid #EC4B42;
+  //        }
+  //        .couponInfo {
+  //          p {
+  //            color: #EC4B42;
+  //          }
+  //        }
+  //      }
+  //      .receiveBtn {
+  //        background: #EC4B42;
+  //      }
+  //    }
+  //  }
+  //}
+
+  &.color2{
+    .listItemBox {
+      background-image:url('../../../static/images/coupon/border_L2.png'), url('../../../static/images/coupon/border_R2.png');
+      .listItemBoxInner {
+        border-top: 1px solid #FF7800;
+        border-bottom: 1px solid #FF7800;
+        .itemInfo {
+          color: #FF7800;
+          .amount {
+            border-bottom: 1px solid #FF7800;
+          }
+          .couponInfo {
+            p {
+              color: #FF7800;
+            }
+          }
+        }
+        .receiveBtn {
+          background: #FF7800;
+        }
+      }
+    }
+  }
+
+  &.color3{
+    .listItemBox {
+      background-image:url('../../../static/images/coupon/border_L3.png'), url('../../../static/images/coupon/border_R3.png');
+      .listItemBoxInner {
+        border-top: 1px solid #86A7FF;
+        border-bottom: 1px solid #86A7FF;
+        .itemInfo {
+          color: #86A7FF;
+          .amount {
+            border-bottom: 1px solid #86A7FF;
+          }
+          .couponInfo {
+            p {
+              color: #86A7FF;
+            }
+          }
+        }
+        .receiveBtn {
+          background: #86A7FF;
+        }
+      }
+    }
+  }
+}
+</style>

+ 238 - 0
components/canvasShow/basics/custom.vue

@@ -0,0 +1,238 @@
+<template>
+  <div class="custom" :class="'terminal' + terminal">
+    <div class="rowLayout" v-if="componentContent.layoutType ==='L1' || componentContent.layoutType ==='L2' || componentContent.layoutType ==='L3' || componentContent.layoutType ==='L4'">
+      <div class="customLayout" :style="{'padding':'0 ' + componentContent.pageSpacing + 'px','marginLeft':(-componentContent.imgClearance) +'px'}">
+        <div class="ul clearfix" :class="'layout'+componentContent.layoutType">
+          <div class='li' v-for="(item,index) of componentContent.imgData" :key="index" :style="{'width':getItemValue(item.width) + '%','height':getItemValue(item.height) + '%','left':getItemValue(item.left) + '%','top':getItemValue(item.top) + '%','paddingLeft':componentContent.imgClearance +'px'}">
+            <a class="a-link" @click="jumpLink(item.linkObj)"><img class="img" :src="item.src" v-if="item.src" mode="widthFix"></a>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div v-else :style="{'padding':'0 ' + componentContent.pageSpacing + 'upx'}">
+      <div class="boxLayout" :style="{'paddingBottom':componentContent.maxH !== 0?getItemValue(componentContent.maxH) + '%': '100%'}">
+        <div class="boxLayoutInner">
+          <div class="boxWarp">
+            <div class="customLayout" :style="{'marginLeft':(-componentContent.imgClearance) +'px','top':(-componentContent.imgClearance) +'px'}">
+              <div class="ul clearfix" :class="'layout'+componentContent.layoutType">
+                <div class='li' v-for="(item,index) of componentContent.imgData" :key="index" :style="{'width':getItemValue(item.width) + '%','height':getItemValue(item.height) + '%','left':getItemValue(item.left) + '%','top':getItemValue(item.top) + '%','padding':componentContent.imgClearance +'px 0 0 ' + componentContent.imgClearance +'px'}">
+                  <a class="a-link" @click="jumpLink(item.linkObj)"><img class="img" :src="item.src" v-if="item.src" mode="widthFix"></a>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {funMixin} from '../config/mixin'
+export default {
+  name: 'customComponent',
+  mixins: [funMixin],
+  data () {
+    return {
+    }
+  },
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    componentContent: {
+      type: Object
+    }
+  },
+  methods: {
+    // 计算生成格子百分比
+    getItemValue (val) {
+      const density = parseInt(this.componentContent.density)
+      if (val === 0 || density === 0) {
+        return 0
+      }
+      return (val / density * 10000 / 100.00)// 小数点后两位百分比
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.custom{
+  //width: 710upx;
+  //margin: 0 auto;
+  .boxLayout{
+    position: relative;
+    .boxLayoutInner{
+      padding-bottom: 100%;
+      position: absolute;
+      width: 100%;
+      left: 0;
+      top: 0;
+    }
+    .boxWarp{
+      position: absolute;
+      width: 100%;
+      height: 100%;
+      left: 0;
+      top: 0;
+      overflow: hidden;
+    }
+  }
+  .customLayout{
+    position: relative;
+    .ul{
+      display: flex;
+      flex-wrap: wrap;
+      position: relative;
+    }
+    .li{
+      box-sizing: border-box;
+      .img{
+        width: 100%;
+        display: block;
+      }
+    }
+    .layoutL1 .li{
+      flex: 0 0 100%;
+    }
+    .layoutL2 .li{
+      flex: 0 0 50%;
+    }
+    .layoutL3 .li{
+      flex: 0 0 33.3%;
+    }
+    .layoutL4 .li{
+      flex: 0 0 25%;
+    }
+    .layoutT2B2{
+      padding-bottom: 100%;
+      .li{
+        width: 50%;
+        height: 50%;
+        position: absolute;
+        .img{
+          width: 100%;
+          height: 100%;
+        }
+        &:nth-child(1){
+          left: 0;
+          top: 0;
+        }
+        &:nth-child(2){
+          right: 0;
+          top: 0;
+        }
+        &:nth-child(3){
+          left: 0;
+          bottom: 0;
+        }
+        &:nth-child(4){
+          right: 0;
+          bottom: 0;
+        }
+      }
+    }
+    .layoutL1R2{
+      padding-bottom: 100%;
+      .li{
+        width: 50%;
+        height: 50%;
+        position: absolute;
+        .img{
+          width: 100%;
+          height: 100%;
+        }
+        &:nth-child(1){
+          height: 100%;
+          left: 0;
+          top: 0;
+        }
+        &:nth-child(2){
+          right: 0;
+          top: 0;
+        }
+        &:nth-child(3){
+          right: 0;
+          bottom: 0;
+        }
+      }
+    }
+    .layoutT1B2{
+      padding-bottom: 100%;
+      .li{
+        width: 50%;
+        height: 50%;
+        position: absolute;
+        .img{
+          width: 100%;
+          height: 100%;
+        }
+        &:nth-child(1){
+          width: 100%;
+          left: 0;
+          top: 0;
+        }
+        &:nth-child(2){
+          left: 0;
+          bottom: 0;
+        }
+        &:nth-child(3){
+          right: 0;
+          bottom: 0;
+        }
+      }
+    }
+    .layoutL1T1B2{
+      padding-bottom: 50%;
+      .li{
+        position: absolute;
+        .img{
+          width: 100%;
+          height: 100%;
+        }
+        &:nth-child(1){
+          width: 50%;
+          height: 100%;
+          left: 0;
+          top: 0;
+        }
+        &:nth-child(2){
+          right: 0;
+          top: 0;
+          width: 50%;
+          height: 50%;
+        }
+        &:nth-child(3){
+          left: 50%;
+          bottom: 0;
+          width: 25%;
+          height: 50%;
+        }
+        &:nth-child(4){
+          right: 0;
+          bottom: 0;
+          width: 25%;
+          height: 50%;
+        }
+      }
+    }
+    .layoutaverage{
+      padding-bottom: 100%;
+      .li{
+        position: absolute;
+        .img{
+          width: 100%;
+          height: 100%;
+        }
+      }
+    }
+    // #ifdef MP
+    .layoutaverage{
+      padding-bottom: 91%;
+    }
+    // #endif
+  }
+}
+</style>

+ 300 - 0
components/canvasShow/basics/discount/app/index.vue

@@ -0,0 +1,300 @@
+<template>
+  <div class="hom-pro-list" v-if="productData.products.length">
+    <div class="title">
+      <!-- #ifdef MP-WEIXIN -->
+      <img class="title-img" src="../../../static/images/discount/img-title.png" alt="限时折扣" mode="widthFix"/>
+      <!-- #endif -->
+      <!-- #ifdef H5 || APP-PLUS -->
+      <image class="title-img" src="../../../static/images/discount/img-title.png" alt="限时折扣" mode="widthFix"/>
+      <!-- #endif -->
+    </div>
+    <div v-if="componentContent.arrangeType == '横向滑动' && productData.products.length > 2" class="product-list">
+      <swiper ref="mySwiper" class="swiper product-list-box" :circular="true" :indicator-dots="false" :autoplay="true" :display-multiple-items="2" @change="swiperChange">
+        <swiper-item class="swiper-slide product-list-item-warp" v-for="(item,index) in productData.products.slice(0, 10)" :key="index" @click="jumpProductDetail(item)">
+          <div class="product-list-item">
+          <div class="product-list-img">
+            <img class="img default-img" :src="item.image">
+          </div>
+          <div class="product-list-info">
+            <label class="product-name">{{item.productName}}</label>
+            <div>
+              <div class="flag">
+                <!-- #ifdef MP-WEIXIN -->
+                <img class="icon" src="../../../static/images/discount/flag-discount2.png" alt="限时折扣" mode="widthFix"/>
+                <!-- #endif -->
+                <!-- #ifdef H5 || APP-PLUS -->
+                <image class="icon" src="../../../static/images/discount/flag-discount2.png" alt="限时折扣" mode="widthFix"/>
+                <!-- #endif -->
+              </div>
+              <label class="buy-count">剩余{{item.stockNumber}}件</label>
+            </div>
+            <div class="price-warp">
+              <div class="price">
+                ¥ {{item.price}}
+              </div>
+              <div class="original-price">
+                ¥ {{item.originalPrice}}
+              </div>
+            </div>
+          </div>
+          </div>
+        </swiper-item>
+      </swiper>
+      <view class="swiper-dots" v-if="productData.products && productData.products.length > 2">
+        <text class="dot" :class="index - swiperCurrent <= 1 && index - swiperCurrent >=0  && 'dot-active'" v-for="(dot, index) in productData.products.length"
+              :key="index"></text>
+      </view>
+<!--      <div class="pagination discount-pagination" slot="pagination"></div>-->
+    </div>
+    <div v-else class="product-list">
+      <div class="product-list-box" >
+        <div class="product-list-item-warp" v-for="(item,index) in productData.products" :key="index" @click="jumpProductDetail(item)">
+          <div class="product-list-item">
+          <div class="product-list-img">
+            <img class="img default-img" :src="item.image">
+          </div>
+          <div class="product-list-info">
+            <label class="product-name">{{item.productName}}</label>
+            <div>
+              <div class="flag">
+                <!-- #ifdef MP-WEIXIN -->
+                <img class="icon" src="../../../static/images/discount/flag-discount2.png" alt="限时折扣" mode="widthFix"/>
+                <!-- #endif -->
+                <!-- #ifdef H5 || APP-PLUS -->
+                <image class="icon" src="../../../static/images/discount/flag-discount2.png" alt="限时折扣" mode="widthFix"/>
+                <!-- #endif -->
+              </div>
+              <label class="buy-count">剩余{{item.stockNumber}}件</label>
+            </div>
+            <div class="price-warp">
+              <div class="price">
+                ¥ {{item.price}}
+              </div>
+              <div class="original-price">
+                ¥ {{item.originalPrice}}
+              </div>
+            </div>
+          </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <button v-show="componentContent.showMore" class="btn-more" @click="jumpDiscount(productData)">查看全部 <span class="icon iconfont icon-arrow-right"></span></button>
+  </div>
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+export default {
+  mixins: [commonMixin],
+  data () {
+    return {
+      swiperCurrent: 0,
+    }
+  },
+  methods:{
+    swiperChange(e) {
+      this.swiperCurrent = e.detail.current;
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.hom-pro-list{
+  padding: 20px 0;
+  .title{
+    text-align: center;
+    margin-bottom: 20upx;
+    .title-img{
+      width: 203upx;
+    }
+  }
+  /**多行多列**/
+  .product-list {
+    position: relative;
+    &-box {
+      display: flex;
+      flex-wrap: wrap;
+      flex-direction: row;
+      padding-left: 20upx;
+      &.swiper{
+        height: 620upx;
+      }
+    }
+    &.product-swiper .product-list-box{
+      margin: 0 20upx;
+      padding-left: 0;
+    }
+    &-item-warp{
+      margin: 0 0 20upx 0;
+    }
+    &-item {
+      width: 348upx;
+      padding: 0 7upx;
+      box-sizing: content-box;
+    }
+    &-img {
+      width: 348upx;
+      height: 348upx;
+      background-color: #f5f5f5;
+      border-radius: 10upx 10upx 0 0;
+      .img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+      }
+    }
+    &-info {
+      background-color: #FFFFFF;
+      //box-shadow: 0px 0px 15px 0px rgba(52, 52, 52, 0.15);
+      border-radius: 0 0 10upx 10upx;
+      padding: 20upx;
+      label{
+        font-weight: normal;
+      }
+      .product-name{
+        font-size: 28upx;
+        color: #333;
+        display: block;
+        overflow: hidden;
+        text-overflow:ellipsis;
+        white-space: nowrap;
+        margin-bottom: 18upx;
+        line-height: 40upx;
+      }
+      .shop-box{
+        background-color: #333333;
+        border-radius: 0px 20upx 20upx 0upx;
+        line-height: 40upx;
+        display: inline-block;
+        height: 40upx;
+        margin-right: 10upx;
+        .shop-name{
+          font-size: 20upx;
+          color: #FFEBC4;
+          padding: 0 8px 0 12upx;
+        }
+        .shop-logo{
+          border: 2px solid #707070;
+          border-radius: 50%;
+          overflow: hidden;
+          float: right;
+          img{
+            width: 34upx;
+            height: 34upx;
+            display: block;
+          }
+        }
+      }
+      .flag{
+        float: left;
+        margin-right: 20upx;
+        .icon{
+          width: 100rpx;
+          height: 40rpx;
+          display: block;
+        }
+      }
+      .buy-count{
+        color: #C5AA7B;
+        font-size: 20upx;
+        border: 2upx solid #E4E5E6;
+        line-height: 40upx;
+        padding: 0 5upx;
+        display: inline-block;
+      }
+      .price-warp{
+        display: flex;
+        align-items: baseline;
+        line-height: 56upx;
+        margin-top: 16upx;
+        .price{
+          color: #C83732;
+          font-size: 40upx;
+          margin-right: 20upx;
+        }
+        .original-price{
+          font-size: 24upx;
+          color: #ccc;
+          text-decoration: line-through;
+        }
+      }
+    }
+    //::v-deep .swiper-pagination-bullet{
+    //  display: none;
+    //}
+  }
+}
+
+//::v-deep .uni-swiper-dots{
+//  display: flex;
+//  justify-content: center;
+//  padding: 10upx 0;
+//  .uni-swiper-dot{
+//    width: 10upx;
+//    height: 10upx;
+//    background: #333333;
+//    opacity: 0.3;
+//    border-radius: 5upx;
+//    margin: 0 5upx;
+//    &-active{
+//      width: 20upx;
+//      height: 10upx;
+//      opacity: 1;
+//    }
+//  }
+//}
+.swiper-dots {
+  display: flex;
+  position: absolute;
+  left: 50%;
+  transform: translateX(-50%);
+  bottom: 15rpx;
+  z-index: 66;
+  .dot {
+    width: 10upx;
+    height: 10upx;
+    background: #333333;
+    opacity: 0.3;
+    border-radius: 5upx;
+    margin: 0 10upx;
+  }
+
+  .dot-active {
+    width: 20upx;
+    opacity: 1;
+  }
+}
+//.pagination{
+//  display: flex;
+//  justify-content: center;
+//  padding: 20upx 0;
+//  ::v-deep .swiper-pagination-bullet{
+//    width: 10upx;
+//    height: 10upx;
+//    background: #333333;
+//    opacity: 0.3;
+//    border-radius: 5upx;
+//    margin: 0 5upx;
+//  }
+//  ::v-deep .swiper-pagination-bullet-active{
+//    width: 20upx;
+//    height: 10upx;
+//    opacity: 1;
+//  }
+//}
+.btn-more {
+  width: 170upx;
+  height: 54upx;
+  line-height: 54upx;
+  border: 2upx solid #C5AA7B;
+  color: #C5AA7B;
+  font-size: 24upx;
+  background-color: transparent;
+  margin: 20upx auto 0;
+  display: flex;
+  align-items: center;
+}
+
+</style>

+ 110 - 0
components/canvasShow/basics/discount/mixin.js

@@ -0,0 +1,110 @@
+// import { directive, Swiper, SwiperSlide } from 'vue-awesome-swiper'
+// import 'swiper/css/swiper.css'
+import api from '../../config/api'
+import {funMixin} from '../../config/mixin'
+
+export const commonMixin = {
+  name: 'discountList',
+  mixins: [funMixin],
+  data () {
+    return {
+      value: 100,
+      productData: {
+        products: []
+      },
+      count: [],
+      timer: null
+    }
+  },
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    typeId: {
+      type: Number,
+      default: 1
+    },
+    shopId: {
+      type: Number,
+      default: 0
+    },
+    componentContent: {
+      type: Object
+    }
+  },
+  // components: {
+  //   Swiper,
+  //   SwiperSlide
+  // },
+  // directives: {
+  //   swiper: directive
+  // },
+  watch: {
+    'componentContent': {
+      handler(newVal, oldVal) {
+        this.getData()
+      },
+      deep: true
+    }
+  },
+  created() {
+    this.getData()
+  },
+  methods: {
+    getData() {
+      const _ = this
+      if(_.componentContent.discountId){
+        var _url= ''
+        if(this.typeId === 1){
+          _url= `${api.getMinDiscount}?ids=${_.componentContent.discountId}`
+        }
+        if(this.typeId === 3){
+          _url= `${api.getDiscounts}?shopId=${_.shopId}&ids=${_.componentContent.discountId}`
+        }
+        const params = {
+          method: 'GET',
+          url: _url,
+        }
+        this.sendReq(params, (res) => {
+          if(res.data.length> 0){
+            _.productData = res.data[0]
+            // 只有进行中和未开始活动, 用倒计时
+            if(_.productData.state !==2) {
+              this.timer = setInterval(()=>{
+                _.getTime(_.productData)
+              }, 1000)
+            }
+          }
+        })
+      } else {
+        _.productData = {
+          products:[]
+        }
+      }
+    },
+    getTime(info) {
+      const date = new Date().getTime()
+      const startTime = new Date(info.startTime.replace(/-/g,'/')).getTime()
+      const endTime = new Date(info.endTime.replace(/-/g,'/')).getTime()
+      if(startTime > date) {
+        this.countDown(startTime-date,true) // 未开始
+      } else {
+        this.countDown(endTime-date) // 进行中
+      }
+
+    },
+    countDown(time, isStart) {
+      const fn = (v) =>  v < 10 ? `0${v}` : v
+      const t = parseInt(time / 1000)
+      const text = isStart ? '开始' : '结束'
+      const hour = parseInt(t / 3600)
+      const min = parseInt((t % 3600) / 60)
+      const s = t % 60
+      this.count = [text, fn(hour), fn(min), fn(s)]
+    }
+  },
+  beforeDestroy() {
+    clearInterval(this.timer)
+  }
+}

+ 247 - 0
components/canvasShow/basics/discount/pc/index.vue

@@ -0,0 +1,247 @@
+<template>
+  <div class="discount">
+    <div class="discount-top">
+      <div class="discount-top-text">全场5折起</div>
+      <div class="discount-top-time">
+<!--        距{{count[0]}}-->
+        距离本场结束还有
+        <div class="time"><span>{{count[1]}}</span>:<span>{{count[2]}}</span>:<span>{{count[3]}}</span></div></div>
+    </div>
+    <div class="discount-more" :style="{backgroundImage: 'url('+ componentContent.moreBg +')'}">
+      <div class="discount-more-overlay">
+        <button class="btn-more" @click="jumpDiscount(productData)">查看全部</button>
+      </div>
+    </div>
+    <div class="discount-list">
+      <div class="swiper-button-prev"></div>
+      <div class="swiper-button-next"></div>
+      <swiper class="products-swiper" :options="swiperOption">
+        <swiper-slide class="products-swiper-slide item" v-for="(item,index) in productData.products" :key="index">
+          <div class="a-link" @click="jumpProductDetail(item)">
+            <div class="itemImgBox">
+              <div class="imgBox">
+                <el-image
+                  :src="item.image"
+                  fit="contain"></el-image>
+              </div>
+            </div>
+            <div class="text">
+              <h4 class="h4">{{item.productName}}</h4>
+              <div class="priceBox">
+                <span class="discount" v-if="item.originalPrice">¥{{item.originalPrice}}</span>
+                <dl>
+                  <dt><img src="../../../static/images/discount/flag-discount.png" alt="折扣价"></dt>
+                  <dd>
+                    ¥{{item.price}}
+                  </dd>
+                </dl>
+              </div>
+            </div>
+          </div>
+        </swiper-slide>
+      </swiper>
+    </div>
+  </div>
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+export default {
+  mixins: [commonMixin],
+  data () {
+    return {
+      swiperOption: {
+        slidesPerView: 3, // 显示数量
+        spaceBetween: 13, // 间隔
+        autoplay: false, // 可选选项,自动滑动
+        loop: true,
+        pagination: {
+          el: '.discount-pagination'
+        },
+        navigation: {
+          nextEl: '.swiper-button-next',
+          prevEl: '.swiper-button-prev'
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.discount{
+  width: 1200px;
+  max-width: 100%;
+  margin: 0 auto;
+  overflow: hidden;
+  &-top{
+    height: 250px;
+    padding-top: 96px;
+    margin-bottom: 15px;
+    background: url("../../../static/images/discount/bg-discount-top.png") no-repeat;
+    &-text{
+      background: url("../../../static/images/discount/bg-discount-top-text.png") no-repeat;
+      width: 176px;
+      height: 83px;
+      padding-top: 13px;
+      line-height: 50px;
+      font-size: 25px;
+      color: #fff;
+      margin: 0px auto 18px;
+      text-align: center;
+    }
+    &-time{
+      margin: 0 auto;
+      text-align: center;
+      font-size: 16px;
+      color: #FFEBC4;
+      .time{
+        font-size: 20px;
+        color: #999;
+        display: inline-block;
+        span{
+          display: inline-block;
+          line-height: 40px;
+          padding: 0 9px;
+          margin: 0 5px;
+          background-color: #343434;
+          color: #fff;
+        }
+      }
+    }
+  }
+  &-list{
+    margin-right: 303px;
+    position: relative;
+    .swiper-button-prev,.swiper-button-next{
+      width: 95px;
+      height: 95px;
+      position: absolute;
+      cursor:pointer;
+      top: 115px;
+      background-repeat: no-repeat;
+      &:after{
+        content: '';
+      }
+    }
+    .swiper-button-prev{
+      left: -22px;
+      background: url('../../../static/images/btn-prev2.png');
+    }
+    .swiper-button-next{
+      right: -22px;
+      background: url('../../../static/images/btn-next2.png');
+    }
+    .a-link{
+      cursor: pointer;
+      &:hover{
+        box-shadow: 3px 4px 20px 0px rgba(186, 186, 186, 0.5);
+      }
+      .itemImgBox {
+        height: auto;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        .imgBox {
+          padding-bottom: 100%;
+          background-color: #cacaca;
+          position: relative;
+          .el-image {
+            width: 100%;
+            height: 100%;
+            position: absolute;
+            top: 0;
+            left: 0;
+          }
+        }
+      }
+      .text{
+        padding:25px 20px 17px;
+        text-align: center;
+        //height: 180px;
+        .h4{
+          font-size: 18px;
+          line-height: 24px;
+          overflow: hidden;
+          text-overflow:ellipsis;
+          white-space: nowrap;
+          color: #333333;
+          //max-height: 48px;
+        }
+        .p{
+          color: #999;
+          font-size: 16px;
+          overflow: hidden;
+          text-overflow:ellipsis;
+          white-space: nowrap;
+          padding-top: 18px;
+          position: relative;
+          margin-top: 8px;
+          &:after{
+            position: absolute;
+            top: 0;
+            left: 50%;
+            margin-left: -80px;
+            width: 160px;
+            height: 2px;
+            background: #F0F0F0;
+            content: '';
+          }
+        }
+        .priceBox {
+          dl {
+            display: inline-block;
+            min-width: 130px;
+            dt{
+              float: left;
+              img{
+                display: block;
+              }
+            }
+            dd{
+              border: 1px solid #F3F4F5;
+              font-size: 25px;
+              line-height: 34px;
+              color: #C83732;
+              margin-left: 57px;
+              padding: 0 10px;
+            }
+          }
+          span.discount {
+            display: block;
+            font-size: 18px;
+            line-height: 24px;
+            padding: 15px 0 11px;
+            color: #ccc;
+            text-decoration: line-through;
+          }
+        }
+      }
+    }
+  }
+  &-more{
+    width: 290px;
+    height: 466px;
+    float: right;
+    position: relative;
+    &-overlay{
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      background-color: rgba(0,0,0,0.6);
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      .btn-more{
+        width: 130px;
+        height: 41px;
+        background-color: #fff;
+        font-size: 18px;
+        color: #C5AA7B;
+      }
+    }
+  }
+}
+</style>

+ 213 - 0
components/canvasShow/basics/group/app/index.vue

@@ -0,0 +1,213 @@
+<template>
+  <div class="group-list" v-if="productData.products&&productData.products.length>0">
+    <div class="group-warp">
+      <div class="title">
+        <label>
+          <!-- #ifdef MP-WEIXIN -->
+          <img class="title-img" src="../../../static/images/group/img-title.png" alt="拼团专区" mode="widthFix"/>
+          <!-- #endif -->
+          <!-- #ifdef H5 || APP-PLUS -->
+          <image class="title-img" src="../../../static/images/group/img-title.png" alt="拼团专区" mode="widthFix"/>
+          <!-- #endif -->
+         </label>
+         <a v-show="componentContent.showMore" class="btn-all a-link" @click="jumpGroupWorks(productData)">更多<i class="iconfont icon-arrow-right"></i></a>
+       </div>
+       <div>
+       <swiper class="swiper pro-box" :circular="true" :indicator-dots="false" :autoplay="true" :display-multiple-items="3" @change="swiperChange">
+         <swiper-item class="swiper-slide pro-item-warp" v-for="(item,index) in productData.products" :key="index" @click="jumpProductDetail(item)">
+          <div class="pro-item-inner">
+            <div class="pro-item">
+              <div class="pro-item-img">
+                <img class="img default-img" :src="item.image">
+              </div>
+              <div class="pro-item-info">
+                <label class="name">{{item.productName}}</label>
+                <div class="price">
+                  <label class="unit">¥ </label>
+                  <label class="val"> {{item.price}}</label>
+                </div>
+                <label class="buyCount">{{item.workUsers?item.workUsers:0}}人已拼</label>
+              </div>
+            </div>
+          </div>
+         </swiper-item>
+         <!-- #ifdef MP-WEIXIN -->
+         <swiper-item v-if="productData.products.length" class="swiper-slide pro-item-warp" v-for="item in (3 - productData.products.length)">
+         </swiper-item>
+         <!-- #endif -->
+       </swiper>
+         <view class="swiper-dots" v-if="productData.products && productData.products.length > 3">
+           <text class="dot" :class="index - swiperCurrent <= 2 && index - swiperCurrent >=0  && 'dot-active'" v-for="(dot, index) in productData.products.length"
+                 :key="index"></text>
+         </view>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+export default {
+  mixins: [commonMixin],
+  data () {
+    return {
+      swiperCurrent: 0,
+    }
+  },
+  methods:{
+    swiperChange(e) {
+      this.swiperCurrent = e.detail.current;
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+  .group-list{
+    padding: 30upx 20upx 60upx;
+    .group-warp{
+      width: 710upx;
+      height: 528upx;
+      padding: 0 2upx;
+      background: #333333;
+      box-shadow: 0px 20upx 30upx rgba(0, 0, 0, 0.3);
+      opacity: 1;
+      border-radius: 20upx;
+      position: relative;
+    }
+    .title{
+      display: flex;
+      align-items:center;
+      position: relative;
+      padding: 40upx 0 25upx 20upx;
+      .title-img{
+        width: 189upx;
+          height: 34rpx;
+      }
+      .btn-all{
+        position: absolute;
+        right: 8upx;
+        top: 40upx;
+        line-height: 33upx;
+        padding-right: 25upx;
+        font-size: 24upx;
+        color: #FFEBC4;
+        .iconfont{
+          content: '';
+          font-size: 26upx;
+          position: absolute;
+          right: 0;
+          top: 0;
+        }
+      }
+    }
+    .pro-box{
+      height: 390upx;
+      display: flex;
+      &.swiper-disabled{
+        .uni-swiper-wrapper{
+          position: static;
+        }
+      }
+      .pro-item-warp{
+        width: 236px;
+      }
+      .pro-item{
+        width: 220upx;
+        height: 382upx;
+        background: #FFFFFF;
+        .pro-item-img{
+          .img{
+            width: 100%;
+            height: 220upx;
+          }
+        }
+        &-inner{
+          padding: 0 8upx;
+        }
+        .pro-item-info{
+          text-align: center;
+          padding: 0px 10upx 20upx;
+          .name{
+            font-size: 24upx;
+            font-weight: normal;
+            color: #FFEBC4;
+            line-height: 50upx;
+            background-color: #333333;
+            text-align: center;
+            margin-bottom: 18upx;
+            padding: 0 5px;
+            display: block;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+          }
+          .price{
+            color: #C83732;
+            font-size: 28upx;
+            font-weight: bold;
+            line-height: 40upx;
+          }
+          .buyCount{
+            font-size: 24upx;
+            color: #ccc;
+            line-height: 34upx;
+            font-weight: normal;
+          }
+        }
+      }
+    }
+    ::v-deep .uni-swiper-dots{
+      display: flex;
+      justify-content: center;
+      bottom: 27px;
+      .uni-swiper-dot{
+        width: 24upx;
+        height: 4upx;
+        background: #fff;
+        opacity: 0.5;
+        border-radius: 2upx;
+        margin: 0 5upx;
+        &-active{
+          opacity: 1;
+        }
+      }
+    }
+
+    //.pagination{
+    //  display: flex;
+    //  justify-content: center;
+    //  ::v-deep .swiper-pagination-bullet{
+    //    width: 24upx;
+    //    height: 4upx;
+    //    background: #fff;
+    //    opacity: 0.5;
+    //    border-radius: 2upx;
+    //    margin: 0 5upx;
+    //  }
+    //  ::v-deep .swiper-pagination-bullet-active{
+    //    opacity: 1;
+    //  }
+    //}
+    .swiper-dots {
+      display: flex;
+      position: absolute;
+      left: 50%;
+      transform: translateX(-50%);
+      bottom: 15rpx;
+      z-index: 66;
+      .dot {
+        width: 24upx;
+        height: 4upx;
+        background: #fff;
+        opacity: 0.5;
+        border-radius: 2upx;
+        margin: 0 10upx;
+      }
+
+      .dot-active {
+        opacity: 1;
+      }
+    }
+  }
+</style>

+ 86 - 0
components/canvasShow/basics/group/mixin.js

@@ -0,0 +1,86 @@
+// import { directive, Swiper, SwiperSlide } from 'vue-awesome-swiper'
+// import 'swiper/css/swiper.css'
+import api from '../../config/api'
+import {funMixin} from '../../config/mixin'
+
+export const commonMixin = {
+  name: 'productList',
+  mixins: [funMixin],
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    typeId: {
+      type: Number,
+      default: 1
+    },
+    shopId: {
+      type: Number,
+      default: 0
+    },
+    componentContent: {
+      type: Object
+    }
+  },
+  // components: {
+  //   Swiper,
+  //   SwiperSlide
+  // },
+  // directives: {
+  //   swiper: directive
+  // },
+  data () {
+    return {
+      productData: {
+        products: []
+      }
+    }
+  },
+  watch: {
+    'componentContent': {
+      handler(newVal, oldVal) {
+        this.getData()
+      },
+      deep: true
+    }
+  },
+  created() {
+    this.getData()
+  },
+  methods: {
+      getData() {
+        const _ = this
+        let _url = ''
+        if(_.typeId === 1){
+          const params = {
+            method: 'GET',
+            url: `${api.getAdminGroupWorks}`,
+          }
+          this.sendReq(params, (res) => {
+            _.productData.products = res.data
+            if (_.productData.products.length > 2) {
+              _.productData.show = true
+            } else {
+              _.productData.show = false
+            }
+          })
+        } else if(_.typeId === 3) {
+          if(_.componentContent.shopGroupWorkId){
+            const params = {
+              method: 'GET',
+              url: `${api.getGroupWorks}?shopId=${_.shopId}&ids=${_.componentContent.shopGroupWorkId}`,
+            }
+            this.sendReq(params, (res) => {
+              _.productData = res.data[0]
+            })
+          } else {
+            _.productData = {
+              products:[]
+            }
+          }
+        }
+
+      },
+  }
+}

+ 250 - 0
components/canvasShow/basics/group/pc/index.vue

@@ -0,0 +1,250 @@
+<template>
+  <div class="product-list" :class="'terminal'+terminal">
+    <div class="picListWarp" v-if="componentContent.arrangeType == '横向滑动'">
+      <div class="picList" v-if="productData.products && productData.products.length>0">
+        <div class="swiper-button-prev"></div>
+        <div class="swiper-button-next"></div>
+        <swiper class="products-swiper" :options="swiperOption">
+          <swiper-slide class="products-swiper-slide item" v-for="(item,index) in productData.products" :key="index">
+            <div class="a-link" @click="jumpProductDetail(item)">
+              <div class="itemImgBox">
+                <div class="imgBox">
+                  <el-image
+                    :src="item.image"
+                    fit="contain"></el-image>
+                </div>
+              </div>
+              <div class="text">
+                <h4 class="h4">{{item.productName}}</h4>
+                <div class="priceBox">
+                  <span class="discount" v-if="item.originalPrice">¥{{item.originalPrice}}</span>
+                  <dl>
+                    <dt><img src="../../../static/images/group/flag-group.png" alt="拼团价"></dt>
+                    <dd>
+                      ¥{{item.price}}
+                    </dd>
+                  </dl>
+                </div>
+              </div>
+            </div>
+          </swiper-slide>
+        </swiper>
+      </div>
+    </div>
+    <div v-else class="picList" >
+      <ul class="clearfix" :class="'imgTextNum' +  componentContent.productNum"  v-if="productData.products && productData.products.length>0">
+        <li class="item" v-for="(item,index) in productData.products.slice(0, componentContent.productRowNum * componentContent.productNum)" :key="index">
+          <div class="a-link" @click="jumpProductDetail(item)">
+            <div class="itemImgBox">
+              <div class="imgBox">
+                <el-image
+                  :src="item.image"
+                  fit="contain"></el-image>
+              </div>
+            </div>
+            <div class="text">
+              <h4 class="h4">{{item.productName}}</h4>
+              <div class="priceBox">
+                <span class="discount" v-if="item.originalPrice">¥{{item.originalPrice}}</span>
+                <dl>
+                  <dt><img src="../../../static/images/group/flag-group.png" alt="拼团价"></dt>
+                  <dd>
+                    ¥{{item.price}}
+                  </dd>
+                </dl>
+              </div>
+            </div>
+          </div>
+        </li>
+      </ul>
+    </div>
+    <button v-show="componentContent.showMore" class="btn-more" @click="jumpGroupWorks(productData)">查看全部 <span class="icon iconfont icon-arrow-right"></span></button>
+  </div>
+</template>
+
+<script>
+  import {commonMixin} from '../mixin'
+  export default {
+    mixins: [commonMixin],
+    data () {
+      return {
+        swiperOption: {
+          slidesPerView: 4, // 显示数量
+          spaceBetween: 13, // 间隔
+          autoplay: false, // 可选选项,自动滑动
+          loop: true,
+          pagination: {
+            el: '.group-pagination'
+          },
+          navigation: {
+            nextEl: '.swiper-button-next',
+            prevEl: '.swiper-button-prev'
+          }
+        }
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+.product-list{
+  padding: 20px 0;
+  background-color: #fff;
+  .picListWarp{
+    width: 1380px;
+    max-width: 100%;
+    margin: 0 auto;
+    position: relative;
+  }
+  .picList{
+    width: 1200px;
+    max-width: 100%;
+    margin: 0 auto;
+    .swiper-button-prev,.swiper-button-next{
+      width: 50px;
+      height: 50px;
+      position: absolute;
+      cursor:pointer;
+      top: 140px;
+      background-repeat: no-repeat;
+      &:after{
+        content: '';
+      }
+    }
+    .swiper-button-prev{
+      left: 0;
+      background: url('../../../static/images/btn-prev.png');
+    }
+    .swiper-button-next{
+      right: 0;
+      background: url('../../../static/images/btn-next.png');
+    }
+    .a-link{
+      cursor: pointer;
+      &:hover{
+        box-shadow: 3px 4px 20px 0px rgba(186, 186, 186, 0.5);
+      }
+      .itemImgBox {
+        height: auto;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        .imgBox {
+          padding-bottom: 100%;
+          background-color: #cacaca;
+          position: relative;
+          .el-image {
+            width: 100%;
+            height: 100%;
+            position: absolute;
+            top: 0;
+            left: 0;
+          }
+        }
+      }
+      .text{
+        padding:25px 20px 17px;
+        text-align: center;
+        //height: 180px;
+        .h4{
+          font-size: 18px;
+          line-height: 24px;
+          overflow: hidden;
+          text-overflow:ellipsis;
+          white-space: nowrap;
+          color: #333333;
+          //max-height: 48px;
+        }
+        .p{
+          color: #999;
+          font-size: 16px;
+          overflow: hidden;
+          text-overflow:ellipsis;
+          white-space: nowrap;
+          padding-top: 18px;
+          position: relative;
+          margin-top: 8px;
+          &:after{
+            position: absolute;
+            top: 0;
+            left: 50%;
+            margin-left: -80px;
+            width: 160px;
+            height: 2px;
+            background: #F0F0F0;
+            content: '';
+          }
+        }
+        .priceBox {
+          dl {
+            display: inline-block;
+            min-width: 130px;
+            dt{
+              float: left;
+              img{
+                display: block;
+              }
+            }
+            dd{
+              border: 1px solid #F3F4F5;
+              font-size: 25px;
+              line-height: 34px;
+              color: #C83732;
+              margin-left: 57px;
+              padding: 0 10px;
+            }
+          }
+          span.discount {
+            display: block;
+            font-size: 18px;
+            line-height: 24px;
+            padding: 15px 0 11px;
+            color: #ccc;
+            text-decoration: line-through;
+          }
+        }
+      }
+    }
+    ul{
+      margin: -15px 0 0 -15px;
+      display: flex;
+      flex-wrap: wrap;
+      li{
+        flex: 0 0 50%;
+        padding: 15px 0 0 15px;
+        width: 0;
+      }
+    }
+    .imgTextNum2 {
+      li {
+        flex: 0 0 50%;
+      }
+    }
+    .imgTextNum3 {
+      li {
+        flex: 0 0 33.33%;
+      }
+    }
+    .imgTextNum4 {
+      li {
+        flex: 0 0 25%;
+      }
+    }
+    .imgTextNum5 {
+      li {
+        flex: 0 0 20%;
+      }
+    }
+  }
+}
+.btn-more {
+  width: 130px;
+  height: 41px;
+  border: 2px solid #C5AA7B;
+  color: #C5AA7B;
+  font-size: 18px;
+  background-color: transparent;
+  margin: 20px auto 0;
+  display: block;
+}
+</style>

+ 131 - 0
components/canvasShow/basics/header/app/index.vue

@@ -0,0 +1,131 @@
+<template>
+	<view class="header">
+		<view class="top-box">
+			<image v-if="componentContent.logoType === 1" class="logo" :src="componentContent.imageUrl" mode="heightFix">
+			</image>
+			<view
+				v-else class="h3"
+				:style="{ fontSize: componentContent.fontSizeNum + 'px', fontWeight: componentContent.textFontW, color: componentContent.titColor }"
+			>
+				{{ componentContent.title }}
+			</view>
+			<view class="search-btn" @click="searchPro">
+				<image class="search-icon" src="../../../../../static/images/origin/search.png" mode="widthFix"></image>
+			</view>
+		</view>
+		<view class="tabs-nav-warp">
+			<scroll-view class="tabs-nav" scroll-x="true">
+				<view class="ul">
+					<view class="li" :class="{ 'on': activeTab === 0 }" @click="tabChange(0)">首页</view>
+					<view
+						v-for="(item, index) in classifyData" :key="index" class="li" :class="{ 'on': activeTab === index + 1 }"
+						@click="tabChange(index + 1, item.id)"
+					>
+						{{ item.categoryName }}
+					</view>
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { commonMixin } from '../mixin'
+export default {
+	mixins: [ commonMixin ],
+	data() {
+		return {
+			activeTab: 0
+		}
+	},
+	computed: {
+
+	},
+	methods: {
+		tabChange(index, id) {
+			this.activeTab = index
+			this.$emit('tabChange', index, id)
+		},
+		searchPro(key, type) {
+			uni.navigateTo({
+				url: `/pages_category_page1/search/index/index`
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.header {
+	.top-box {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding-left: 30upx;
+		width: 100%;
+
+		.logo {
+			// width: 280upx;
+			height: 70upx;
+			margin-top: 0upx;
+		}
+
+		.search-btn {
+			height: 66upx;
+			background: rgba(255, 255, 255, 1);
+			border-radius: 33upx;
+			display: flex;
+			flex-direction: row;
+			align-items: center;
+			margin-right: 30upx;
+
+			.search-icon {
+				width: 60upx;
+				height: 60upx;
+			}
+		}
+	}
+}
+
+.tabs-nav-warp {
+	margin-top: 20upx;
+	padding: 0 30upx;
+	overflow: hidden;
+
+	.tabs-nav {
+		.ul {
+			display: flex;
+			flex-wrap: nowrap;
+			justify-content: space-between;
+
+			.li {
+				flex: 1 0 auto;
+				margin-left: 36upx;
+				font-size: 30upx;
+				color: #999999;
+				position: relative;
+				padding-bottom: 18upx;
+				text-align: center;
+
+				&:first-child {
+					margin-left: 0;
+				}
+
+				&.on {
+					&:after {
+						content: '';
+						width: 100%;
+						height: 4upx;
+						background: #C5AA7B;
+						position: absolute;
+						left: 0;
+						bottom: 0;
+					}
+
+					font-weight:bold;
+				}
+			}
+		}
+	}
+}
+</style>

+ 47 - 0
components/canvasShow/basics/header/mixin.js

@@ -0,0 +1,47 @@
+import api from '../../config/api'
+import { funMixin } from '../../config/mixin'
+
+export const commonMixin = {
+  name: 'headerComponent',
+  mixins: [funMixin],
+  props: {
+    terminal: {
+      type: Number,
+      default: 4,
+    },
+    typeId: {
+      type: Number,
+      default: 1,
+    },
+    shopId: {
+      type: Number,
+      default: 0,
+    },
+    componentContent: {
+      type: Object,
+    },
+  },
+  data() {
+    return {
+      classifyData: [],
+    }
+  },
+  mounted() {
+    this.getData()
+  },
+  methods: {
+    getData() {
+      const _ = this
+      _.sendReq(
+        {
+          url: `${api.getClassify}?page=1&pageSize=20`,
+          method: 'GET',
+        },
+        (res) => {
+          _.classifyData = res.data
+        },
+        (err) => {}
+      )
+    },
+  },
+}

+ 119 - 0
components/canvasShow/basics/imageText.vue

@@ -0,0 +1,119 @@
+<template>
+  <div class="imageText warp" :class="['terminal'+terminal,'pos-' + componentContent.positionValue]">
+    <div class="img img-left">
+      <a class="item a-link" @click="jumpLink(componentContent.linkObj)"><img :src="componentContent.imageUrl" alt=""></a>
+    </div>
+    <div class="text">
+      <h3 class="h3">{{componentContent.title}}</h3>
+      <div v-html="componentContent.content"></div>
+    </div>
+    <div class="img img-right">
+      <a class="item a-link" @click="jumpLink(componentContent.linkObj)"><img :src="componentContent.imageUrl" alt=""></a>
+    </div>
+  </div>
+</template>
+
+<script>
+  import {funMixin} from '../config/mixin'
+  export default {
+    name: 'imageTextComponent',
+    mixins: [funMixin],
+    data () {
+      return {
+      }
+    },
+    props: {
+      terminal: {
+        type: Number,
+        default: 4
+      },
+      componentContent: {
+        type: Object
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .imageText{
+	  width: 710upx;
+	  margin: 0 auto;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 20upx 0;
+    .img{
+      width: 50%;
+      padding-bottom: 30%;
+      background-color: #cacaca;
+      position: relative;
+      img{
+        max-width: 100%;
+        height: 100%;
+        max-height: 100%;
+        position: absolute;
+        margin: auto;
+        top: 0;
+        right: 0;
+        bottom: 0;
+        left: 0;
+      }
+    }
+    .text{
+      width: 40%;
+      .h3{
+        font-size: 30upx;
+        margin-bottom: 24upx;
+      }
+      .p{
+        font-size: 16upx;
+      }
+    }
+    &.pos-top{
+      display: block;
+      text-align: center;
+      .img{
+        width: 100%;
+      }
+      .text{
+        width: 100%;
+        margin-top: 30upx;
+      }
+      .img-right{
+        display: none;
+      }
+    }
+    &.pos-bottom{
+      display: block;
+      text-align: center;
+      .img{
+        width: 100%;
+      }
+      .text{
+        width: 100%;
+        margin-bottom: 30upx;
+      }
+      .img-left{
+        display: none;
+      }
+    }
+    &.pos-left{
+      .img-right{
+        display: none;
+      }
+    }
+    &.pos-right{
+      .text{
+        padding-left: 20upx;
+      }
+      .img-left{
+        display: none;
+      }
+    }
+
+  }
+  .terminal1,.terminal2,.terminal3{
+    width: 710upx;
+    margin: 0 auto;
+  }
+</style>

+ 136 - 0
components/canvasShow/basics/imageTextList.vue

@@ -0,0 +1,136 @@
+<template>
+  <div class="hom-pro-list warp" :class="'terminal'+terminal">
+    <div class="title">
+      <h2 class="h2" :style="{textAlign:componentContent.textAlign}">{{componentContent.title}}</h2>
+    </div>
+    <div class="ul clearfix" :class="{imgTextNum4: componentContent.imgTextData.length === 4, imgTextNum5: componentContent.imgTextData.length === 5, imgTextStyle: componentContent.imgTextData.length >= 6 || componentContent.imgTextData.length === 3}">
+      <div class="li" v-for="(item,index) in componentContent.imgTextData" :key="index">
+        <a class="item a-link" @click="jumpLink(item.linkObj)">
+          <div class="itemImgBox" v-show="item.isShow">
+            <div class="imgBox">
+              <img ref="getHeight" :src="item.imgData" v-show="item.imgData" :alt="item.title">
+            </div>
+          </div>
+          <div class="text">
+            <h4 class="h4">{{item.title}}</h4>
+            <p class="p">{{item.describe}}</p>
+          </div>
+        </a>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+  import {funMixin} from '../config/mixin'
+  export default {
+    name: 'imageTextList',
+    mixins: [funMixin],
+    props: {
+      terminal: {
+        type: Number,
+        default: 4
+      },
+      componentContent: {
+        type: Object
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .hom-pro-list{
+    min-height: 450upx;
+    padding: 20upx 0;
+    .title{
+      margin-bottom: 23upx;
+      position: relative;
+      .h2{
+        font-size: 22upx;
+        color: #333;
+        line-height: 1em;
+        font-weight: bold;
+      }
+    }
+    .ul{
+      margin: -15upx 0 0 -15upx;
+      display: flex;
+      flex-wrap: wrap;
+      .li{
+        flex: 0 0 50%;
+        padding: 15upx 0 0 15upx;
+        box-sizing: border-box;
+        .item{
+          .itemImgBox {
+            height: auto;
+            display: flex;
+            flex-direction: column;
+            justify-content: center;
+            .imgBox {
+              padding-bottom: 80%;
+              background-color: #cacaca;
+              position: relative;
+              img {
+                max-width: 100%;
+                height: 100%;
+                max-height: 100%;
+                position: absolute;
+                margin: auto;
+                top: 0;
+                right: 0;
+                bottom: 0;
+                left: 0;
+              }
+            }
+          }
+          .text{
+            padding:16upx 20upx;
+            text-align: center;
+            .h4{
+              line-height: 25upx;
+              overflow: hidden;
+              color: #333333;
+            }
+            .p{
+              color: #666666;
+              padding: 5upx 0 10upx;
+            }
+          }
+        }
+      }
+    }
+    .imgTextNum4 {
+      .li {
+        flex: 0 0 50%;
+      }
+    }
+    .imgTextNum5 {
+      .li {
+        flex: 0 0 33.33%;
+      }
+      .li:nth-child(1) {
+        flex: 0 0 50%;
+      }
+      .li:nth-child(2) {
+        flex: 0 0 50%;
+      }
+    }
+    .imgTextStyle {
+      .li {
+        flex: 0 0 33.33%;
+      }
+    }
+  }
+  @media screen and (max-width: 768px) {
+    .hom-pro-list .ul .li{
+      flex: 0 0 50%;
+    }
+  }
+  .terminal1,.terminal2,.terminal3{
+    width: 710upx;
+    margin: 0 auto;
+    &.hom-pro-list .ul .li{
+      flex: 0 0 50%;
+    }
+  }
+</style>

+ 101 - 0
components/canvasShow/basics/imageTextNav.vue

@@ -0,0 +1,101 @@
+<template>
+  <div class="ul image-text-nav" :class="'terminal' + terminal">
+    <div class="li" v-for="(item,index) in componentContent.imgTextData" :key="index" :style="{'flex':'0 0 '+ getItemValue() + '%'}" @click="jumpLink(item.linkObj)">
+      <!--<router-link class="item" :to="jumpLink(item.linkObj)">-->
+        <div class="img-box">
+          <div class="img-box-inner">
+			  <div class="imgBox">
+			    <img class="img" :src="item.img"/>
+			  </div>
+          </div>
+        </div>
+        <h4 class="h4">{{item.title}}</h4>
+      <!--</router-link>-->
+    </div>
+  </div>
+</template>
+
+<script>
+  import {funMixin} from '../config/mixin'
+  export default {
+    name: 'imageTextNav',
+    mixins: [funMixin],
+    props: {
+      terminal: {
+        type: Number,
+        default: 4
+      },
+      componentContent: {
+        type: Object
+      }
+    },
+    methods: {
+      // 计算生成格子百分比
+      getItemValue (val) {
+        const len = parseInt(this.componentContent.imgTextData.length)
+        if (len === 0) {
+          return 0
+        } else {
+          return (1 / len * 10000 / 100.00)
+        }
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .image-text-nav{
+    min-height: 100upx;
+    width: 710upx;
+    margin: 0 auto;
+    display: flex;
+    padding: 20upx 0;
+    .li{
+      text-align: center;
+      .img-box{
+        .imgBox{
+          width: 100upx;
+          height: 100upx;
+		  display: inline-block;
+		  .img{
+			  width: 100%;
+			  height: 100%;
+			  object-fit: cover;
+		  }
+        }
+      }
+      .h4{
+        font-size: 26upx;
+        color: #333;
+        line-height: 33upx;
+      }
+    }
+    &.terminal4{
+      width: 1000upx;
+      .li{
+        .img-box{
+          display: inline-block;
+          width: 100upx;
+          height: 100upx;
+          box-shadow: 0 10upx 30upx rgba(51, 51, 51, 0.15);
+          &-inner{
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            height: 100%;
+          }
+          .imgBox{
+            width: 60upx;
+            height: 60upx;
+          }
+        }
+        .h4{
+          font-size: 18upx;
+          color: #ccc;
+          line-height: 1em;
+          padding-top: 20upx;
+        }
+      }
+    }
+  }
+</style>

+ 71 - 0
components/canvasShow/basics/live/app/index.vue

@@ -0,0 +1,71 @@
+<template>
+  <div class="live-list-page" v-if="roomList && roomList.length > 0">
+    <div class="title">
+      <!-- #ifdef MP-WEIXIN -->
+      <img class="title-img" src="../../../static/images/live/img-title.png" alt="直播"  mode="widthFix"/>
+      <!-- #endif -->
+      <!-- #ifdef H5 || APP-PLUS -->
+      <image class="title-img" src="../../../static/images/live/img-title.png" alt="直播"  mode="widthFix"/>
+      <!-- #endif -->
+    </div>
+    <div class="live-list">
+      <LiveBox class="live-item"
+        v-for="item in roomList"
+        :key="item.roomid"
+        :liveData.sync="item"
+        @click="toLiveRoom(item)"
+      />
+    </div>
+    <button v-show="componentContent.showMore" class="btn-more" @click="jumpLive()">查看全部 <span class="icon iconfont icon-arrow-right"></span></button>
+  </div>
+</template>
+
+<script>
+// const NET = require('../../../../../utils/request')
+// const API = require('../../../../../config/api')
+import {commonMixin} from '../mixin'
+import LiveBox from './item.vue'
+export default {
+  mixins: [commonMixin],
+  components: {
+    LiveBox
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.live-list-page{
+  .title{
+    text-align: center;
+    margin-bottom: 20rpx;
+    .title-img{
+      width: 211rpx;
+      height: 32rpx;
+    }
+  }
+  .live-list{
+    padding-left: 20rpx;
+    width: 100%;
+    display: flex;
+    flex-wrap: wrap;
+    .live-item{
+      margin:0 14rpx 14rpx 0;
+      width: 348rpx;
+      height: 464rpx;
+      border-radius: 8rpx;
+      overflow: hidden;
+    }
+  }
+  .btn-more {
+    width: 170rpx;
+    height: 54rpx;
+    border: 2rpx solid #C5AA7B;
+    color: #C5AA7B;
+    font-size: 24rpx;
+    background-color: transparent;
+    margin: 20rpx auto 0;
+    display: flex;
+    align-items: center;
+  }
+}
+</style>

+ 483 - 0
components/canvasShow/basics/live/app/item.vue

@@ -0,0 +1,483 @@
+<template>
+	<view class="live-box">
+		<view v-if="liveData.liveStatus === 101" class="live-ongoing" @click="toLive">
+			<image class="cover-img" :src="liveData.feedsImg" />
+			<view class="status">
+				<view class="status-state">
+					<!-- #ifdef MP-WEIXIN -->
+					<img class="img" src="../../../static/images/live/icon-live-num.png" mode="widthFix" />
+					<!-- #endif -->
+					<!-- #ifdef H5 || APP-PLUS -->
+					<image class="img" src="../../../static/images/live/icon-live-num.png" mode="widthFix" />
+					<!-- #endif -->
+					直播中
+				</view>
+				<!-- <view class="status-num">1000人</view> -->
+			</view>
+			<view class="user">
+				<view class="user-pic">
+					<image class="img" :src="liveData.anchorHeadImg" />
+				</view>
+				<view class="user-name">{{ liveData.anchorNickName }}</view>
+			</view>
+			<view class="products">
+				<view class="uni-padding-wrap">
+					<view class="page-section swiper">
+						<view class="page-section-spacing">
+							<swiper
+								class="swiper" :indicator-dots="indicatorDots" :autoplay="autoplay" :interval="interval"
+								:duration="duration" :vertical="true"
+							>
+								<swiper-item v-for="item in liveData.goods" :key="item.goods_id">
+									<view class="swiper-item">{{ item.name }}</view>
+								</swiper-item>
+							</swiper>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<view v-else class="live-other" @click="toLive">
+			<image class="cover-img" :src="liveData.feedsImg" />
+			<div class="filter-box-warp">
+				<div class="filter-box">
+					<image class="cover-img" :src="liveData.feedsImg" />
+				</div>
+			</div>
+			<view class="user">
+				<view class="user-pic">
+					<image class="img" :src="liveData.anchorHeadImg" />
+				</view>
+				<view class="user-name">{{ liveData.anchorNickName }}</view>
+			</view>
+			<view v-if="liveStatus === 102" class="count-down">
+				<image v-if="isLate" class="img" src="@/static/images/live/live-late.png" />
+				<view class="text">{{ liveTimeTitle }}</view>
+				<view v-if="!isLate" class="time">
+					<view class="time-item">{{ times[0] }}</view>
+					<view class="dot">:</view>
+					<view class="time-item">{{ times[1] }}</view>
+					<view class="dot">:</view>
+					<view class="time-item">{{ times[2] }}</view>
+				</view>
+			</view>
+			<!-- #ifdef MP-WEIXIN -->
+			<view
+				v-if="liveStatus === 102 && !isLate" class="btn-subscribe" :class="{ subscribed: subscribeLive === '已预约' }"
+				@click.stop="onSubscribe"
+			>
+				{{ subscribeLive }}
+			</view>
+			<!-- #endif -->
+			<view v-if="liveStatus === 103" class="endContainer">
+				<view class="endBox">
+					<view></view>
+					<view></view>
+					<view></view>
+					<view></view>
+				</view>
+				<view>直播已结束</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+const NET = require('@/utils/request')
+const API = require('@/config/api')
+
+import { startLiveTemplate } from '@/config/subscribe.js'
+import { liveAppid } from '@/config/live.js'
+
+// #ifdef MP-WEIXIN
+const livePlayer = requirePlugin('live-player-plugin')
+// #endif
+export default {
+	props: {
+		liveData: {
+			type: Object,
+			default: () => ({
+				roomId: 0,
+				anchorNickName: '',
+				feedsImg: '' // 官方收录封面
+			})
+		}
+	},
+	data() {
+		return {
+			background: ['color1', 'color2', 'color3'],
+			indicatorDots: false,
+			autoplay: true,
+			interval: 2000, // 自动播放间隔时长
+			duration: 500, // 幻灯片切换时长(ms)
+			d: 0,
+			m: 0,
+			s: 0,
+			times: [],
+			liveStatus: 100,
+			liveTimeTitle: '开播倒计时',
+			subscribeLive: '立即预约',
+			timer: null,
+			isLate: false
+		}
+	},
+	created() {
+		this.liveStatus = this.liveData.liveStatus
+		this.subscribeLive = this.liveData.subscribeStatus === 0 ? '立即预约' : '已预约'
+		this.getStatus()
+		this.countTime()
+		// this.getSubscribeStatus()
+	},
+	destroyed() {
+		clearTimeout(this.timer)
+	},
+	methods: {
+		getStatus() {
+			if (!this.liveData.roomId) { return }
+			const _this = this
+			// #ifdef MP-WEIXIN
+			livePlayer.getLiveStatus({ room_id: this.liveData.roomId })
+				.then((res) => {
+					// 101: 直播中, 102: 未开始, 103: 已结束, 104: 禁播, 105: 暂停中, 106: 异常,107:已过期
+					// _this.liveData.liveStatus = res.liveStatus
+					_this.liveStatus = res.liveStatus
+				})
+				.catch((err) => {
+				})
+			this.timer = setInterval(() => {
+				livePlayer.getLiveStatus({ room_id: this.liveData.roomId })
+					.then((res) => {
+						// 101: 直播中, 102: 未开始, 103: 已结束, 104: 禁播, 105: 暂停中, 106: 异常,107:已过期
+						_this.liveStatus = res.liveStatus
+						this.countTime()
+					})
+					.catch((err) => {
+						throw new Error(err)
+					})
+			}, 60000)
+			// #endif
+		},
+		changeIndicatorDots(e) {
+			this.indicatorDots = !this.indicatorDots
+		},
+		changeAutoplay(e) {
+			this.autoplay = !this.autoplay
+		},
+		intervalChange(e) {
+			this.interval = e.target.value
+		},
+		durationChange(e) {
+			this.duration = e.target.value
+		},
+		countTime() {
+			var nowtime = new Date().getTime()  // 获取当前时间
+			const starttime = new Date(this.liveData.startTime).getTime()
+			if (this.liveStatus === 102) {
+				if (starttime > nowtime) {
+					var lefttime = starttime - nowtime  // 距离结束时间的毫秒数
+					var leftd = Math.floor(lefttime / (1000 * 60 * 60))  // 计算小时数
+					var leftm = Math.floor(lefttime / (1000 * 60) % 60)  // 计算分钟数
+					var lefts = Math.floor(lefttime / 1000 % 60)  // 计算秒数
+					this.times = [leftd < 10 ? '0' + leftd : leftd, leftm < 10 ? '0' + leftm : leftm, lefts < 10 ? '0' + lefts : lefts]
+					this.liveTimeTitle = '开播倒计时'
+					setTimeout(() => {
+						this.countTime()
+					}, 1000)
+				} else {
+					this.times = ['00', '00', '00']
+					this.isLate = true
+					this.liveTimeTitle = '正在赶来的路上...'
+				}
+			}
+		},
+		toLive() {
+			if (!liveAppid || !this.liveData) { return }
+			// 跳转直播间携带路由参数
+			// let customParams = encodeURIComponent(JSON.stringify({ path: 'livePage/index', pid: 1 }))
+			// #ifdef MP-WEIXIN
+			wx.navigateTo({
+				url: `plugin-private://${liveAppid}/pages/live-player-plugin?room_id=${this.liveData.roomId}`
+				// url: `plugin-private://${liveAppid}/pages/live-player-plugin?room_id=${this.liveData.roomId}&custom_params=${customParams}`
+			})
+			// #endif
+		},
+		onSubscribe() {
+			if (this.subscribeLive === '立即预约') {
+				const _this = this
+				// #ifdef MP-WEIXIN
+				uni.requestSubscribeMessage({
+					tmplIds: [ startLiveTemplate ],
+					success(res) {
+						if (res[startLiveTemplate] === 'accept') {
+							NET.request(API.SubScribeLive, { id: _this.liveData.id }, 'post')
+								.then((res) => {
+									if (res.data) {
+										_this.subscribeLive = '已预约'
+									} else {
+										uni.showToast({
+											title: res.message || '订阅失败,请稍后再试!',
+											icon: 'none'
+										})
+									}
+								})
+								.catch((err) => {
+									uni.showToast({
+										title: res.message || '订阅失败,请稍后再试!',
+										icon: 'none'
+									})
+								})
+						}
+					}
+				})
+				// #endif
+			}
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.live-box {
+	position: relative;
+	color: #fff;
+	width: 100%;
+	height: 100%;
+
+	.cover-img {
+		width: 100%;
+		height: 100%;
+		position: absolute;
+		z-index: 0;
+	}
+
+	.user {
+		display: flex;
+		line-height: 60rpx;
+		height: 64rpx;
+
+		&-pic {
+			.img {
+				width: 60rpx;
+				height: 60rpx;
+				border: 2px solid rgba(255, 255, 255, 0.5019607843137255);
+				border-radius: 50%;
+				overflow: hidden;
+			}
+		}
+
+		&-name {
+			font-size: 28rpx;
+			margin-left: 16rpx;
+			overflow: hidden;
+			text-overflow: ellipsis;
+			white-space: nowrap;
+			width: 245rpx;
+		}
+	}
+
+	.live-ongoing {
+		width: 100%;
+		height: 100%;
+		position: relative;
+
+		.status {
+			position: absolute;
+			top: 22rpx;
+			left: 22rpx;
+			//width: 212upx;
+			height: 48rpx;
+			// background: rgba(0,0,0,0.3);
+			// border: 2rpx solid rgba(255,255,255,0.3);
+			border-radius: 24rpx;
+			font-size: 20rpx;
+			line-height: 44rpx;
+			display: flex;
+
+			// padding-right: 8rpx;
+			&-state {
+				width: 118rpx;
+				height: 44rpx;
+				background: linear-gradient(90deg, #C83732 0%, #E25C44 100%);
+				opacity: 1;
+				border-radius: 26rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+
+				.img {
+					width: 20rpx;
+					height: 20rpx;
+					margin-right: 6rpx;
+				}
+			}
+
+			&-num {
+				min-width: 80rpx;
+				padding: 0 8rpx;
+			}
+		}
+
+		.user {
+			position: absolute;
+			bottom: 62rpx;
+			left: 20rpx;
+		}
+
+		.products {
+			position: absolute;
+			left: 0rpx;
+			bottom: 20rpx;
+			width: 100%;
+			padding: 0 20rpx;
+
+			.swiper {
+				height: 34rpx;
+				line-height: 34rpx;
+				font-size: 24rpx;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+			}
+		}
+	}
+
+	.live-other {
+		position: relative;
+		height: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		.filter-box-warp {
+			background-color: #000000;
+			position: absolute;
+			top: 0;
+			left: 0;
+			width: 100%;
+			height: 100%;
+
+			.filter-box {
+				position: absolute;
+				top: -30rpx;
+				left: -30rpx;
+				width: 348rpx;
+				height: 464rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				-webkit-filter: blur(20px);
+				-moz-filter: blur(21px);
+				-ms-filter: blur(20px);
+				-o-filter: blur(20px);
+				padding: 30rpx;
+				box-sizing: content-box;
+			}
+		}
+
+		.user {
+			position: absolute;
+			top: 20rpx;
+			left: 20rpx;
+		}
+
+		.count-down {
+			position: relative;
+
+			.text {
+				font-size: 26rpx;
+				line-height: 36rpx;
+				margin-bottom: 16rpx;
+				opacity: 0.5;
+				text-align: center;
+			}
+
+			.img {
+				display: block;
+				width: 80rpx;
+				height: 80rpx;
+				margin: 16rpx auto;
+			}
+
+			.time {
+				display: flex;
+				justify-content: space-around;
+				align-items: center;
+
+				&-item {
+					min-width: 52rpx;
+					padding: 0 5rpx;
+					height: 52rpx;
+					line-height: 52rpx;
+					background: #FFFFFF;
+					opacity: 1;
+					border-radius: 6rpx;
+					font-size: 26rpx;
+					color: #C83732;
+					text-align: center;
+
+					.dot {
+						line-height: 52rpx;
+					}
+				}
+			}
+		}
+
+		.btn-subscribe {
+			width: 200rpx;
+			height: 64rpx;
+			line-height: 64rpx;
+			background: linear-gradient(90deg, #C83732 0%, #E25C44 100%);
+			box-shadow: 0rpx 6rpx 12rpx rgba(233, 0, 0, 0.3);
+			opacity: 1;
+			border-radius: 6rpx;
+			color: #fff;
+			font-size: 24rpx;
+			text-align: center;
+			position: absolute;
+			bottom: 60rpx;
+			left: 50%;
+			margin-left: -100rpx;
+
+			&.subscribed {
+				background: #FFFFFF;
+				color: #999999;
+				box-shadow: none;
+			}
+		}
+
+		.endContainer {
+			position: relative;
+
+			.endBox {
+				width: 40%;
+				height: 60rpx;
+				margin: 20rpx auto;
+				display: flex;
+				justify-content: space-between;
+				align-items: flex-end;
+
+				view {
+					width: 0;
+					border: 2rpx solid #FFF;
+				}
+
+				view:nth-of-type(1) {
+					height: 20%;
+				}
+
+				view:nth-of-type(2) {
+					height: 50%;
+				}
+
+				view:nth-of-type(3) {
+					height: 30%;
+				}
+
+				view:nth-of-type(4) {
+					height: 70%;
+				}
+			}
+		}
+	}
+}
+</style>

+ 61 - 0
components/canvasShow/basics/live/mixin.js

@@ -0,0 +1,61 @@
+const NET = require('@/utils/request')
+const API = require('@/config/api')
+import { funMixin } from '../../config/mixin'
+
+export const commonMixin = {
+	mixins: [ funMixin ],
+	data() {
+		return {
+			appid: 'wx123456789abcdefg',
+			roomId: [], // 填写具体的房间号
+			roomList: [],
+			page: {
+				page: 1,
+				pageSize: 6
+			}
+		}
+	},
+	props: {
+		terminal: {
+			type: Number,
+			default: 4
+		},
+		typeId: {
+			type: Number,
+			default: 1
+		},
+		shopId: {
+			type: Number,
+			default: 0
+		},
+		componentContent: {
+			type: Object
+		}
+	},
+	created() {
+		this.getLiveRooms()
+	},
+	methods: {
+		// 获取直播间列表
+		getLiveRooms() {
+			NET.request(API.LiveRoomes, this.page, 'get').then((res) => {
+				this.roomList = res.data.list
+			})
+		},
+		toLiveRoom(item) {
+			this.roomId.push(item.roomid)
+			if (!this.appid || !this.roomId.length) {
+				return
+			}
+			// 路由参数
+			const customParams = encodeURIComponent(JSON.stringify({ path: 'livePage/index', pid: 1 }))
+			// let customParams
+			// 开发者在直播间页面路径上携带自定义参数(如示例中的path和pid参数),后续可以在分享卡片链接和跳转至商详页时获取,详见【获取自定义参数】、【直播间到商详页面携带参数】章节(上限600个字符,超过部分会被截断)
+			// #ifdef MP-WEIXIN
+			wx.navigateTo({
+				url: `plugin-private://${this.appid}/pages/live-player-plugin?room_id=${this.roomId}&custom_params=${customParams}`
+			})
+			// #endif
+		}
+	}
+}

+ 228 - 0
components/canvasShow/basics/newProduct/app/index.vue

@@ -0,0 +1,228 @@
+<template>
+	<div v-if="productData.length > 0" class="hom-pro-list">
+		<div class="product-swiper">
+			<div class="product-swiper-box">
+				<div v-for="(item, index) in productData.slice(0, 3)" :key="index" class="product-swiper-warp">
+					<div class=" product-swiper-item" @click="jumpProductDetail(item)">
+						<div class="product-swiper-img">
+							<img class="img pic-img default-img" :src="item.image">
+						</div>
+						<div class="product-swiper-info">
+							<label class="product-name">{{ item.productName }}</label>
+							<div class="price-warp">
+								<!-- #ifdef MP-WEIXIN -->
+								<img v-if="item.activityType == 1" class="iconImg" src="../../../static/images/groupBuyIcon.png">
+								<img v-if="item.activityType == 2" class="iconImg" src="../../../static/images/spikeIcon.png">
+								<img v-if="item.activityType == 4" class="iconImg" src="../../../static/images/spikeIcon.png">
+								<img v-if="item.activityType == 3" class="iconImg" src="../../../static/images/discountListIcon.png">
+								<img v-if="item.activityType == 5" class="iconImg" src="../../../static/images/discountListIcon.png">
+								<img v-if="item.activityType == 9" class="iconImg" src="../../../static/images/memberCenterIcon.png">
+								<img
+									v-if="item.activityType == 8" class="iconImg"
+									src="../../../../../static/images/origin/jierizhekou.png"
+								>
+								<!-- #endif -->
+								<!-- #ifdef H5 || APP-PLUS -->
+								<image v-if="item.activityType == 1" class="iconImg" src="../../../static/images/groupBuyIcon.png">
+								</image>
+								<image v-if="item.activityType == 2" class="iconImg" src="../../../static/images/spikeIcon.png"></image>
+								<image v-if="item.activityType == 4" class="iconImg" src="../../../static/images/spikeIcon.png"></image>
+								<image v-if="item.activityType == 3" class="iconImg" src="../../../static/images/discountListIcon.png">
+								</image>
+								<image v-if="item.activityType == 5" class="iconImg" src="../../../static/images/discountListIcon.png">
+								</image>
+								<image v-if="item.activityType == 9" class="iconImg" src="../../../static/images/memberCenterIcon.png">
+								</image>
+								<image
+									v-if="item.activityType == 8" class="iconImg"
+									src="../../../../../static/images/origin/jierizhekou.png"
+								>
+								</image>
+								<!-- #endif -->
+								<div class="price">
+									¥ {{ item.price }}
+								</div>
+								<!--                <div class="original-price"> -->
+								<!--                  ¥ {{item.originalPrice}} -->
+								<!--                </div> -->
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="pagination new-pagination"></div>
+		</div>
+		<button v-show="componentContent.showMore" class="btn-more" @click="jumpProList(componentContent.productData)">
+			查看全部
+			<span class="icon iconfont icon-arrow-right"></span>
+		</button>
+	</div>
+</template>
+
+<script>
+import { commonMixin } from '../mixin'
+export default {
+	mixins: [ commonMixin ]
+	// data () {
+	// return {
+	//   index: 1,
+	//   swiperOption: {
+	//     slidesPerView: 3,
+	//     spaceBetween: 12,
+	//     autoplay: false, // 可选选项,自动滑动
+	//     loop: true,
+	//     pagination: {
+	//       el: '.new-pagination'
+	//     }
+	//   }
+	// }
+	// }
+}
+</script>
+
+<style lang="scss" scoped>
+.hom-pro-list {
+	::v-deep .swiper-wrapper {
+		position: static;
+	}
+
+	/**横向滑动**/
+	.product-swiper {
+		width: 100%;
+		height: 454upx;
+		padding: 90upx 34upx 0;
+		background: url("../../../static/images/newProduct/bg-product-card.png") no-repeat center;
+		background-size: 710upx 454upx;
+		box-sizing: border-box;
+		position: relative;
+
+		&+.btn-more {
+			margin-top: 20upx;
+		}
+
+		.title {
+			padding: 22upx 0upx 0 0;
+
+			label {
+				background-image: none;
+				color: #A56C4C;
+				font-style: italic;
+				padding: 0;
+			}
+		}
+
+		&-box {
+			padding-bottom: 20upx;
+			display: flex;
+		}
+
+		&-warp {
+			padding: 0 5upx;
+		}
+
+		&-item {
+			width: 220upx;
+			position: relative;
+			background-color: #FFFFFF;
+		}
+
+		&-img {
+			width: 220upx;
+			height: 220upx;
+			position: relative;
+
+			&:after {
+				content: '';
+				display: block;
+				width: 54upx;
+				height: 54upx;
+				background: url("../../../static/images/newProduct/flag-new.png") no-repeat;
+				background-size: 100% 100%;
+				position: absolute;
+				top: 0;
+				left: 0;
+			}
+
+			.img {
+				width: 100%;
+				height: 100%;
+				object-fit: contain;
+			}
+		}
+
+		&-info {
+			background-color: #FFFFFF;
+			padding: 10upx;
+			text-align: center;
+
+			.product-name {
+				font-size: 20upx;
+				color: #333;
+				display: block;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+				margin-bottom: 6upx;
+				line-height: 28upx;
+			}
+
+			.price-warp {
+				display: flex;
+				justify-content: center;
+				align-items: center;
+				line-height: 28upx;
+
+				.iconImg {
+					width: 58rpx;
+					height: 36rpx;
+					margin-right: 10rpx;
+				}
+
+				.price {
+					color: #C83732;
+					font-size: 20upx;
+					margin-right: 10upx;
+				}
+
+				.original-price {
+					font-size: 16upx;
+					color: #ccc;
+					text-decoration: line-through;
+				}
+			}
+		}
+	}
+}
+
+.pagination {
+	display: flex;
+	justify-content: center;
+	width: 100%;
+	bottom: 0;
+
+	::v-deep .swiper-pagination-bullet {
+		width: 24upx;
+		height: 4upx;
+		background: #FFFFFF;
+		opacity: 0.5;
+		border-radius: 2upx;
+		margin: 0 10upx;
+	}
+
+	::v-deep .swiper-pagination-bullet-active {
+		opacity: 1;
+	}
+}
+
+.btn-more {
+	width: 170upx;
+	height: 54upx;
+	border: 2upx solid #C5AA7B;
+	color: #C5AA7B;
+	font-size: 24upx;
+	background-color: transparent;
+	margin: 20upx auto 0;
+	display: flex;
+	align-items: center;
+}
+</style>

+ 89 - 0
components/canvasShow/basics/newProduct/mixin.js

@@ -0,0 +1,89 @@
+
+import {funMixin} from '../../config/mixin'
+// import { directive, Swiper, SwiperSlide } from 'vue-awesome-swiper'
+// import 'swiper/css/swiper.css'
+import api from '../../config/api'
+
+export const commonMixin = {
+  name: 'productList',
+   mixins: [funMixin],
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    typeId: {
+      type: Number,
+      default: 1
+    },
+    shopId: {
+      type: Number,
+      default: 0
+    },
+    componentContent: {
+      type: Object
+    }
+  },
+  // components: {
+  //   Swiper,
+  //   SwiperSlide
+  // },
+  // directives: {
+  //   swiper: directive
+  // },
+  data () {
+    return {
+      productData: []
+    }
+  },
+  mounted() {
+    this.getData(true)
+  },
+  watch: {
+    'componentContent': {
+      handler(newVal, oldVal) {
+        this.getData()
+      },
+      deep: true
+    }
+  },
+  methods: {
+      getData(isFirst) {
+        const _ = this
+        if (_.componentContent.productData.sourceType === '1') {
+          if(_.componentContent.productData.productIdList && _.componentContent.productData.productIdList.length>0){
+            _.sendReq({
+              url: `${api.getProductsV2}?page=1&pageSize=99&ids=${_.componentContent.productData.productIdList}`,
+              method: 'GET'
+            }, (proRes) => {
+              _.productData = proRes.data.list
+              if(isFirst){
+                _.componentContent.productData.imgTextData = _.productData
+              }
+              // _.$forceUpdate() // 刷新轮播图
+
+            })
+          } else {
+            _.productData = []
+          }
+        } else if(_.componentContent.productData.sourceType === '2'){
+          if(_.componentContent.productData.categoryId) {
+            _.sendReq({
+              url: `${api.getProductsV2}?page=1&pageSize=99&classifyId=${_.componentContent.productData.categoryId}`,
+              method: 'GET'
+            }, (proRes) => {
+              _.productData = proRes.data.list
+              if(isFirst){
+                _.componentContent.productData.imgTextData = _.productData
+              }
+              _.$forceUpdate() // 刷新轮播图
+              // _.swiper.update()
+            })
+          } else {
+            _.productData = []
+
+        }
+      }
+    }
+  },
+}

+ 96 - 0
components/canvasShow/basics/notice.vue

@@ -0,0 +1,96 @@
+<template>
+  <div class="notice-list" :class="'terminal'+terminal" :style="{backgroundColor:componentContent.bgColor}">
+    <swiper class="swiper-wrapper" :circular="true" :indicator-dots="false" :autoplay="true" :vertical="true">
+      <swiper-item class="swiper-slide" v-for="(item,index) in noticesList" :key="index">
+        <div class="a-link" @click="jumpNoticeDetail(item)" :style="{color:componentContent.titColor}"><span>{{item.noticeTitle}}</span></div>
+      </swiper-item>
+    </swiper>
+  </div>
+</template>
+
+<script>
+import api from '../config/api'
+import { funMixin } from '../config/mixin'
+import { directive, Swiper, SwiperSlide } from 'vue-awesome-swiper'
+import 'swiper/css/swiper.css'
+export default {
+  name: "noticeComponent",
+  mixins: [funMixin],
+  data () {
+    return {
+      noticesList: [],
+    }
+  },
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    componentContent: {
+      type: Object
+    }
+  },
+  mounted() {
+    this.getData()
+  },
+  methods: {
+    getData() {
+      const _ = this
+      let _url = `${api.getNotices}`
+      const params = {
+        method: 'GET',
+        url: _url,
+      }
+      this.sendReq(params, (res) => {
+        _.noticesList = res.data
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.notice-list{
+  height: 60upx;
+  line-height: 60upx;
+  overflow: hidden;
+  .a-link{
+    // display: block;
+    cursor: pointer;
+    overflow: hidden;
+    text-overflow:ellipsis;
+    white-space: nowrap;
+    margin: 0 20upx;
+    span{
+      display: inline-block;
+      padding-left: 50upx;
+      font-size: 24upx;
+      background: url("../static/images/notice/ico_notice2.png") no-repeat left center;
+      background-size: 30upx 30upx;
+    }
+  }
+  &.terminal4{
+    height: 50upx;
+    line-height: 50upx;
+    padding: 0;
+    .swiper-container{
+      height: 100%;
+      width: 1200upx;
+      max-width: 100%;
+      margin: 0 auto;
+    }
+    .a-link{
+      cursor: pointer;
+      display: block;
+      text-align: left;
+      margin: 0 20upx;
+      span{
+        display: block;
+        padding-left: 25upx;
+        font-size: 14upx;
+        background: url("../static/images/notice/ico_notice.png") no-repeat left center;
+      }
+    }
+  }
+}
+</style>

+ 218 - 0
components/canvasShow/basics/price/app/index.vue

@@ -0,0 +1,218 @@
+<template>
+  <div class="group-list" v-if="productData&&productData.composeProducts&&productData.composeProducts.length>0">
+    <div class="group-warp">
+      <div class="title">
+        <label>
+          <!-- #ifdef MP-WEIXIN -->
+          <img class="title-img" src="../../../static/images/price/img-title.png" alt="组合优惠" mode="widthFix"/>
+          <!-- #endif -->
+          <!-- #ifdef H5 || APP-PLUS -->
+          <image class="title-img" src="../../../static/images/price/img-title.png" alt="组合优惠" mode="widthFix"/>
+          <!-- #endif -->
+        </label>
+        <div class="price-text">
+          <swiper class="swiper" :autoplay="true" :vertical="true">
+            <swiper-item v-for="(item,index) in productData.rules" :key="index">
+              {{item.price}}元任选{{item.number}}件
+            </swiper-item>
+          </swiper>
+        </div>
+        <a v-show="componentContent.showMore" class="btn-all a-link" @click="jumpCombination(productData)">更多<i class="iconfont icon-arrow-right"></i></a>
+      </div>
+      <div>
+      <swiper class="swiper pro-box" :indicator-dots="false" :autoplay="true" :display-multiple-items="2" @change="swiperChange">
+        <swiper-item class="pro-item-warp" v-for="(item,index) in productData.composeProducts" :key="index" @click="jumpProductDetail(item)">
+          <div class="pro-item-inner">
+          <div class="pro-item">
+          <div class="pro-item-img">
+            <img class="img default-img" :src="item.image">
+          </div>
+          <div class="pro-item-info">
+            <h3 class="name">
+              {{item.productName}}
+            </h3>
+            <div class="stock">
+              还剩{{item.stockNumber}}件
+            </div>
+            <div class="price-warp">
+              <div class="price">
+                ¥ {{item.price}}
+              </div>
+              <div class="original-price">
+                ¥ {{item.originalPrice}}
+              </div>
+            </div>
+          </div>
+          </div>
+          </div>
+        </swiper-item>
+      </swiper>
+        <view class="swiper-dots" v-if="productData.composeProducts && productData.composeProducts.length > 2">
+          <text class="dot" :class="index - swiperCurrent <= 1 && index - swiperCurrent >=0  && 'dot-active'" v-for="(dot, index) in productData.composeProducts.length"
+                :key="index"></text>
+        </view>
+      </div>
+    </div>
+  </div>
+
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+export default {
+  mixins: [commonMixin],
+  data () {
+    return {
+      swiperCurrent: 0,
+    }
+  },
+  methods:{
+    swiperChange(e) {
+      this.swiperCurrent = e.detail.current;
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.group-list{
+  padding: 30upx 20upx 60upx;
+  .group-warp{
+    height: 544upx;
+    padding: 0 10upx;
+    background: #333333;
+    box-shadow: 0 20upx 30upx rgba(0, 0, 0, 0.3);
+    opacity: 1;
+    border-radius: 20upx;
+    position: relative;
+  }
+  .title{
+    display: flex;
+    align-items:center;
+    position: relative;
+    padding: 32upx 0 20upx 20upx;
+    .title-img{
+      width: 203upx;
+    }
+    .price-text{
+      width: 300upx;
+      height: 50upx;
+      background: linear-gradient(90deg, #C83732 0%, #E25C44 100%);
+      box-shadow: 0 6upx 12upx rgba(233, 0, 0, 0.3);
+      border-radius: 26upx;
+      font-size: 24upx;
+      color: #fff;
+      text-align: center;
+      line-height: 50upx;
+      margin-left: 20upx;
+      .swiper{
+        height: 50upx;
+      }
+    }
+    .btn-all{
+      position: absolute;
+      right: 8upx;
+      top: 40upx;
+      line-height: 33upx;
+      padding-right: 25upx;
+      font-size: 24upx;
+      color: #FFEBC4;
+      .iconfont{
+        content: '';
+        font-size: 26upx;
+        position: absolute;
+        right: 0;
+        top: 0;
+      }
+    }
+  }
+  .pro-box{
+    padding-bottom: 20upx;
+    height: 450upx;
+    .pro-item-inner{
+      padding: 0 10upx;
+    }
+    .pro-item{
+      width: 100%;
+      height: 412upx;
+      background: #FFFFFF;
+      .pro-item-img{
+        .img{
+          width: 100%;
+          height: 236upx;
+        }
+      }
+      .pro-item-info{
+        padding: 0 20upx;
+        .name{
+          font-size: 24upx;
+          line-height: 40upx;
+          color: #333333;
+          overflow: hidden;
+          text-overflow:ellipsis;
+          white-space: nowrap;
+        }
+        .stock{
+          padding: 0 8upx;
+          height: 40upx;
+          border: 2upx solid #E4E5E6;
+          line-height: 40upx;
+          margin: 10upx 0;
+          display: inline-block;
+          font-size: 20upx;
+          color: #C5AA7B;
+        }
+        .price{
+          font-size: 32upx;
+          font-weight: bold;
+          line-height: 44upx;
+          color: #C83732;
+          padding-right: 10upx;
+          display: inline-block;
+        }
+        .original-price{
+          font-size: 20upx;
+          line-height: 28upx;
+          color: #CCCCCC;
+          display: inline-block;
+        }
+      }
+    }
+  }
+  //.pagination{
+  //  display: flex;
+  //  justify-content: center;
+  //  ::v-deep .swiper-pagination-bullet{
+  //    width: 24upx;
+  //    height: 4upx;
+  //    background: #fff;
+  //    opacity: 0.5;
+  //    border-radius: 2upx;
+  //    margin: 0 5upx;
+  //  }
+  //  ::v-deep .swiper-pagination-bullet-active{
+  //    opacity: 1;
+  //  }
+  //}
+  .swiper-dots {
+    display: flex;
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    bottom: 15rpx;
+    z-index: 66;
+    .dot {
+      width: 24upx;
+      height: 4upx;
+      background: #fff;
+      opacity: 0.5;
+      border-radius: 2upx;
+      margin: 0 10upx;
+    }
+
+    .dot-active {
+      opacity: 1;
+    }
+  }
+}
+</style>

+ 73 - 0
components/canvasShow/basics/price/mixin.js

@@ -0,0 +1,73 @@
+import { directive, Swiper, SwiperSlide } from 'vue-awesome-swiper'
+import 'swiper/css/swiper.css'
+import api from '../../config/api'
+import {funMixin} from '../../config/mixin'
+
+export const commonMixin = {
+  name: 'price',
+  mixins: [funMixin],
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    typeId: {
+      type: Number,
+      default: 1
+    },
+    shopId: {
+      type: Number,
+      default: 0
+    },
+    componentContent: {
+      type: Object
+    }
+  },
+  components: {
+    Swiper,
+    SwiperSlide
+  },
+  directives: {
+    swiper: directive
+  },
+  data () {
+    return {
+      productData: {
+        composeProducts: [],
+        rules: []
+      }
+    }
+  },
+  watch: {
+    'componentContent': {
+      handler(newVal, oldVal) {
+        this.getData()
+      },
+      deep: true
+    }
+  },
+  created() {
+    this.getData()
+  },
+  methods: {
+      getData() {
+        const _ = this
+        if(_.componentContent.priceId){
+          const params = {
+            method: 'GET',
+            url: `${api.getPrices}?shopId=${_.shopId}&ids=${_.componentContent.priceId}&page=1&pageSize=10`,
+          }
+          this.sendReq(params, (res) => {
+            if( res.data.length > 0){
+              _.productData = res.data[0]
+            }
+          })
+        } else {
+          _.productData = {
+            composeProducts: [],
+            rules: []
+          }
+        }
+      },
+  }
+}

+ 253 - 0
components/canvasShow/basics/price/pc/index.vue

@@ -0,0 +1,253 @@
+<template>
+  <div class="product-list" :class="'terminal'+terminal">
+    <div class="picListWarp" v-if="componentContent.arrangeType == '横向滑动'">
+      <div class="picList" v-if="productData.products && productData.products.length>0">
+        <div class="swiper-button-prev"></div>
+        <div class="swiper-button-next"></div>
+        <swiper class="products-swiper" :options="swiperOption">
+          <swiper-slide class="products-swiper-slide item" v-for="(item,index) in productData.composeProducts" :key="index">
+            <div class="a-link" @click="jumpProductDetail(item)">
+              <div class="itemImgBox">
+                <div class="imgBox">
+                  <el-image
+                    :src="item.image"
+                    fit="contain"></el-image>
+                </div>
+              </div>
+              <div class="text">
+                <div class="discount-text">
+                  <span>任选{{productData.rules[0].number}}件{{productData.rules[0].price}}元</span>
+                </div>
+                <h4 class="h4">{{item.productName}}</h4>
+                <div class="priceBox">
+                  <span>¥{{item.price}}</span>
+                </div>
+                <button class="btn-cart" @click="addCart(item.id)">加入购物车</button>
+              </div>
+            </div>
+          </swiper-slide>
+        </swiper>
+      </div>
+    </div>
+    <div v-else class="picList" >
+      <ul class="clearfix" :class="'imgTextNum' +  componentContent.productNum">
+        <li class="item" v-for="(item,index) in productData.composeProducts.slice(0, componentContent.productRowNum * componentContent.productNum)" :key="index">
+          <div class="a-link" @click="jumpProductDetail(item)">
+            <div class="itemImgBox">
+              <div class="imgBox">
+                <el-image
+                  :src="item.image"
+                  fit="contain"></el-image>
+              </div>
+            </div>
+            <div class="text">
+              <div class="discount-text">
+                <span>任选{{productData.rules[0].number}}件{{productData.rules[0].price}}元</span>
+              </div>
+              <h4 class="h4">{{item.productName}}</h4>
+              <div class="priceBox">
+                <span>¥{{item.price}}</span>
+              </div>
+              <button class="btn-cart" @click="addCart(item.id)">加入购物车</button>
+            </div>
+          </div>
+        </li>
+      </ul>
+    </div>
+    <button v-show="componentContent.showMore" class="btn-more" @click="jumpPice(productData)">查看全部 <span class="icon iconfont icon-arrow-right"></span></button>
+  </div>
+</template>
+
+<script>
+  import {commonMixin} from '../mixin'
+  export default {
+    mixins: [commonMixin],
+    data () {
+      return {
+        swiperOption: {
+          slidesPerView: 4, // 显示数量
+          spaceBetween: 13, // 间隔
+          autoplay: false, // 可选选项,自动滑动
+          loop: true,
+          pagination: {
+            el: '.price-pagination'
+          },
+          navigation: {
+            nextEl: '.swiper-button-next',
+            prevEl: '.swiper-button-prev'
+          }
+        }
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+.product-list{
+  padding: 20px 0;
+  background-color: #fff;
+  .picListWarp{
+    width: 1380px;
+    max-width: 100%;
+    margin: 0 auto;
+    position: relative;
+  }
+  .picList{
+    width: 1200px;
+    max-width: 100%;
+    margin: 0 auto;
+    .swiper-button-prev,.swiper-button-next{
+      width: 50px;
+      height: 50px;
+      position: absolute;
+      cursor:pointer;
+      top: 140px;
+      background-repeat: no-repeat;
+      &:after{
+        content: '';
+      }
+    }
+    .swiper-button-prev{
+      left: 0;
+      background: url('../../../static/images/btn-prev.png');
+    }
+    .swiper-button-next{
+      right: 0;
+      background: url('../../../static/images/btn-next.png');
+    }
+    .a-link{
+      cursor: pointer;
+      &:hover{
+        box-shadow: 3px 4px 20px 0px rgba(186, 186, 186, 0.5);
+      }
+      .itemImgBox {
+        height: auto;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        .imgBox {
+          padding-bottom: 100%;
+          background-color: #cacaca;
+          position: relative;
+          .el-image {
+            width: 100%;
+            height: 100%;
+            position: absolute;
+            top: 0;
+            left: 0;
+          }
+        }
+      }
+      .text{
+        padding:25px 10px 17px;
+        text-align: center;
+        position: relative;
+        //height: 180px;
+        .discount-text{
+          background: url("../../../static/images/price/bg-discount.png") no-repeat;
+          width: 100%;
+          height: 47px;
+          font-size: 14px;
+          color: #fff;
+          line-height: 60px;
+          text-align: center;
+          position: absolute;
+          top: -47px;
+          left: 0;
+        }
+        .h4{
+          font-size: 18px;
+          line-height: 24px;
+          overflow: hidden;
+          text-overflow:ellipsis;
+          white-space: nowrap;
+          color: #333333;
+          //max-height: 48px;
+        }
+        .p{
+          color: #999;
+          font-size: 16px;
+          overflow: hidden;
+          text-overflow:ellipsis;
+          white-space: nowrap;
+          padding-top: 18px;
+          position: relative;
+          margin-top: 8px;
+          &:after{
+            position: absolute;
+            top: 0;
+            left: 50%;
+            margin-left: -80px;
+            width: 160px;
+            height: 2px;
+            background: #F0F0F0;
+            content: '';
+          }
+        }
+        .priceBox {
+          padding-top: 11px;
+          line-height: 33px;
+          span {
+            font-size: 25px;
+            color: #C83732;
+            padding-right: 12px;
+          }
+          span.discount {
+            font-size: 18px;
+            color: #ccc;
+            text-decoration: line-through;
+          }
+        }
+        .btn-cart{
+          margin-top: 5px;
+          width: 270px;
+          height: 40px;
+          background-color: #333;
+          font-size: 18px;
+          color: #FFEBC4;
+        }
+      }
+    }
+    ul{
+      margin: -15px 0 0 -15px;
+      display: flex;
+      flex-wrap: wrap;
+      li{
+        flex: 0 0 50%;
+        padding: 15px 0 0 15px;
+        width: 0;
+      }
+    }
+    .imgTextNum2 {
+      li {
+        flex: 0 0 50%;
+      }
+    }
+    .imgTextNum3 {
+      li {
+        flex: 0 0 33.33%;
+      }
+    }
+    .imgTextNum4 {
+      li {
+        flex: 0 0 25%;
+      }
+    }
+    .imgTextNum5 {
+      li {
+        flex: 0 0 20%;
+      }
+    }
+  }
+}
+.btn-more {
+  width: 130px;
+  height: 41px;
+  border: 2px solid #C5AA7B;
+  color: #C5AA7B;
+  font-size: 18px;
+  background-color: transparent;
+  margin: 20px auto 0;
+  display: block;
+}
+</style>

+ 416 - 0
components/canvasShow/basics/product/app/index.vue

@@ -0,0 +1,416 @@
+<template>
+	<div class="hom-pro-list">
+		<div class="title">
+			<!-- #ifdef MP-WEIXIN -->
+			<img class="title-img" src="../../../static/images/product/img-title.png" alt="商品推荐" mode="widthFix" />
+			<!-- #endif -->
+			<!-- #ifdef H5 || APP-PLUS -->
+			<image class="title-img" src="../../../static/images/product/img-title.png" alt="商品推荐" mode="widthFix" />
+			<!-- #endif -->
+		</div>
+		<div v-if="componentContent.arrangeType == '横向滑动' && productData.length > 2" class="product-list">
+			<swiper
+				ref="mySwiper" class="swiper product-list-box" :circular="true" :indicator-dots="false"
+				:autoplay="true"
+				:display-multiple-items="2" @change="swiperChange"
+			>
+				<swiper-item v-for="(item, index) in productData" :key="index" class="product-list-item-warp">
+					<div v-if="JSON.stringify(item) !== '{}'" class="product-list-item" @click="jumpProductDetail(item)">
+						<div class="product-list-img">
+							<img class="img pic-img default-img" :src="item.image">
+						</div>
+						<div class="product-list-info">
+							<label class="product-name">{{ item.productName }}</label>
+							<div class="flex">
+								<div v-if="typeId == 1" class="shop-box" @click.stop="jumpStore(item)">
+									<label class="shop-name">{{ item.shopName }}</label>
+									<div class="shop-logo">
+										<img :src="item.shopLogo">
+									</div>
+								</div>
+								<label class="buy-count">已售{{ item.number ? item.number : 0 }}件</label>
+							</div>
+							<div class="price-warp">
+								<!-- #ifdef MP-WEIXIN -->
+								<img v-if="item.activityType == 1" class="iconImg" src="../../../static/images/groupBuyIcon.png">
+								<img v-if="item.activityType == 2" class="iconImg" src="../../../static/images/spikeIcon.png">
+								<img v-if="item.activityType == 4" class="iconImg" src="../../../static/images/spikeIcon.png">
+								<img v-if="item.activityType == 3" class="iconImg" src="../../../static/images/discountListIcon.png">
+								<img v-if="item.activityType == 5" class="iconImg" src="../../../static/images/discountListIcon.png">
+								<img v-if="item.activityType == 9" class="iconImg" src="../../../static/images/memberCenterIcon.png">
+								<img
+									v-if="item.activityType == 8" class="iconImg"
+									src="../../../../../static/images/origin/jierizhekou.png"
+								>
+								<!-- #endif -->
+								<!-- #ifdef H5 || APP-PLUS -->
+								<image v-if="item.activityType == 1" class="iconImg" src="../../../static/images/groupBuyIcon.png">
+									<image v-if="item.activityType == 2" class="iconImg" src="../../../static/images/spikeIcon.png">
+										<image v-if="item.activityType == 4" class="iconImg" src="../../../static/images/spikeIcon.png">
+											<image
+												v-if="item.activityType == 3" class="iconImg"
+												src="../../../static/images/discountListIcon.png"
+											>
+												<image
+													v-if="item.activityType == 5" class="iconImg"
+													src="../../../static/images/discountListIcon.png"
+												>
+													<image
+														v-if="item.activityType == 9" class="iconImg"
+														src="../../../static/images/memberCenterIcon.png"
+													>
+														<image
+															v-if="item.activityType == 8" class="iconImg"
+															src="../../../../../static/images/origin/jierizhekou.png"
+														>
+															<!-- #endif -->
+															<div class="price">
+																¥ {{ item.price }}
+															</div>
+															<div class="original-price">
+																¥ {{ item.originalPrice }}
+															</div>
+														</image>
+													</image>
+												</image>
+											</image>
+										</image>
+									</image>
+								</image>
+							</div>
+						</div>
+					</div>
+					<!-- 自定义骨架屏 -->
+					<div v-else class="product-list-item ske-loading">
+						<div class="product-list-img child-loading">
+
+						</div>
+						<div class="product-list-info">
+							<label class="product-name child-loading"></label>
+							<div class="price-warp child-loading" style="padding: 5px 0">
+							</div>
+							<div class="price-warp child-loading" style="padding: 5px 0">
+							</div>
+						</div>
+					</div>
+				</swiper-item>
+			</swiper>
+			<view v-if="productData && productData.length > 2" class="swiper-dots">
+				<text
+					v-for="(dot, index) in productData.length" :key="index" class="dot"
+					:class="index - swiperCurrent <= 1 && index - swiperCurrent >= 0 && 'dot-active'"
+				></text>
+			</view>
+		</div>
+		<div v-else class="product-list">
+			<ProductSkeleton v-if="isFirst" style="" :loading="loading" :is-first="isFirst" />
+			<div v-else class="product-list-box">
+				<div v-for="(item, index) in productData" :key="index" class="product-list-item-warp">
+					<div class="product-list-item" @click="jumpProductDetail(item)">
+						<div class="product-list-img">
+							<img class="img pic-img default-img" :src="item.image">
+						</div>
+						<div class="product-list-info">
+							<label class="product-name">{{ item.productName }}</label>
+							<div class="flex">
+								<div v-if="typeId == 1" class="shop-box" @click.stop="jumpStore(item)">
+									<label class="shop-name">{{ item.shopName }}</label>
+									<div class="shop-logo">
+										<img :src="item.shopLogo">
+									</div>
+								</div>
+								<label class="buy-count">已售{{ item.number ? item.number : 0 }}件</label>
+							</div>
+							<div class="price-warp">
+								<!-- #ifdef MP-WEIXIN -->
+								<img v-if="item.activityType == 1" class="iconImg" src="../../../static/images/groupBuyIcon.png" />
+								<img v-if="item.activityType == 2" class="iconImg" src="../../../static/images/spikeIcon.png" />
+								<img v-if="item.activityType == 4" class="iconImg" src="../../../static/images/spikeIcon.png" />
+								<img v-if="item.activityType == 3" class="iconImg" src="../../../static/images/discountListIcon.png" />
+								<img v-if="item.activityType == 5" class="iconImg" src="../../../static/images/discountListIcon.png" />
+								<img v-if="item.activityType == 9" class="iconImg" src="../../../static/images/memberCenterIcon.png" />
+								<img
+									v-if="item.activityType == 8" class="iconImg"
+									src="../../../../../static/images/origin/jierizhekou.png"
+								/>
+								<!-- #endif -->
+								<!-- #ifdef H5 || APP-PLUS -->
+								<image v-if="item.activityType == 1" class="iconImg" src="../../../static/images/groupBuyIcon.png" />
+								<image v-if="item.activityType == 2" class="iconImg" src="../../../static/images/spikeIcon.png" />
+								<image v-if="item.activityType == 4" class="iconImg" src="../../../static/images/spikeIcon.png" />
+								<image v-if="item.activityType == 3" class="iconImg" src="../../../static/images/discountListIcon.png" />
+								<image v-if="item.activityType == 5" class="iconImg" src="../../../static/images/discountListIcon.png" />
+								<image v-if="item.activityType == 9" class="iconImg" src="../../../static/images/memberCenterIcon.png" />
+								<image
+									v-if="item.activityType == 8" class="iconImg"
+									src="../../../../../static/images/origin/jierizhekou.png"
+								/>
+								<!-- #endif -->
+								<div class="price">
+									¥ {{ item.price }}
+								</div>
+								<div v-if="item.originalPrice && item.originalPrice > item.price" class="original-price">
+									¥ {{ item.originalPrice }}
+								</div>
+							</div>
+							<div
+								style="width: fit-content;margin-top: -4upx;padding: 2upx 12upx 6upx 2upx;background-color: #f0f0f0;font-size: 28upx;color: #fa5151;border-radius: 0 22upx 22upx 0;vertical-align: middle;"
+							>
+								可使用{{ Math.ceil(Number(item.price || 0)) }}代金券抵扣
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+		<button v-show="componentContent.showMore" class="btn-more" @click="jumpProList(componentContent.productData)">
+			查看全部 <span class="icon iconfont icon-arrow-right"></span>
+		</button>
+	</div>
+</template>
+
+<script>
+import { commonMixin } from '../mixin'
+import ProductSkeleton from '@/components/basics/components/ProductSkeleton'
+export default {
+	components: {
+		ProductSkeleton
+	},
+	mixins: [ commonMixin ],
+	data() {
+		return {
+			swiperCurrent: 0
+		}
+	},
+	methods: {
+		swiperChange(e) {
+			this.swiperCurrent = e.detail.current
+		}
+	}
+}
+</script>
+
+<style
+    lang="scss"
+    scoped
+>
+.hom-pro-list {
+	padding: 20rpx 13rpx;
+
+	.title {
+		text-align: center;
+		margin-bottom: 20rpx;
+
+		.title-img {
+			width: 211rpx;
+		}
+	}
+
+	/**多行多列**/
+	.product-list {
+		position: relative;
+
+		&-box {
+			display: flex;
+			flex-wrap: wrap;
+			flex-direction: row;
+
+			&.swiper {
+				height: 620rpx;
+			}
+		}
+
+		&.product-swiper .product-list-box {
+			padding-left: 0;
+		}
+
+		&-item-warp {
+			margin: 0 0 20rpx 0;
+		}
+
+		&-item {
+			width: 348rpx;
+			padding: 0 7rpx;
+			box-sizing: content-box;
+		}
+
+		&-img {
+			width: 348rpx;
+			height: 348rpx;
+			background-color: #f5f5f5;
+			border-radius: 10rpx 10rpx 0 0;
+
+			.img {
+				width: 100%;
+				height: 100%;
+				object-fit: contain;
+			}
+		}
+
+		&-info {
+			background-color: #FFFFFF;
+			//box-shadow: 0px 0px 15px 0px rgba(52, 52, 52, 0.15);
+			border-radius: 0 0 10rpx 10rpx;
+			padding: 20rpx;
+
+			label {
+				font-weight: normal;
+			}
+
+			.product-name {
+				font-size: 28rpx;
+				color: #333;
+				display: block;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				white-space: nowrap;
+				margin-bottom: 18rpx;
+				line-height: 40rpx;
+			}
+
+			.flex {
+				display: flex;
+				align-items: center;
+			}
+
+			.shop-box {
+				background-color: #333333;
+				border-radius: 0rpx 20rpx 20rpx 0rpx;
+				line-height: 40rpx;
+				display: flex;
+				align-items: center;
+				height: 40rpx;
+				margin-right: 10rpx;
+
+				.shop-name {
+					font-size: 20rpx;
+					color: #FFEBC4;
+					padding: 0 8rpx 0 12rpx;
+				}
+
+				.shop-logo {
+					border: 2rpx solid #707070;
+					border-radius: 50%;
+					overflow: hidden;
+					float: right;
+
+					img {
+						width: 34rpx;
+						height: 34rpx;
+						display: block;
+					}
+				}
+			}
+
+			.buy-count {
+				color: #C5AA7B;
+				font-size: 20rpx;
+				border: 2rpx solid #E4E5E6;
+				line-height: 40rpx;
+				padding: 0 5rpx;
+			}
+
+			.price-warp {
+				display: flex;
+				align-items: baseline;
+				line-height: 56rpx;
+
+				.iconImg {
+					width: 58rpx;
+					height: 36rpx;
+					margin-right: 10rpx;
+				}
+
+				.price {
+					color: #C83732;
+					font-size: 40rpx;
+					margin-right: 20rpx;
+				}
+
+				.original-price {
+					font-size: 24rpx;
+					color: #ccc;
+					text-decoration: line-through;
+				}
+			}
+		}
+
+		//::v-deep .swiper-pagination-bullet{
+		//  display: none;
+		//}
+	}
+}
+
+//::v-deep .uni-swiper-dots{
+//  display: flex;
+//  justify-content: center;
+//  padding: 10rpx 0;
+//  .uni-swiper-dot{
+//    width: 10rpx;
+//    height: 10rpx;
+//    background: #333333;
+//    opacity: 0.3;
+//    border-radius: 5rpx;
+//    margin: 0 5rpx;
+//    &-active{
+//      width: 20rpx;
+//      height: 10rpx;
+//      opacity: 1;
+//    }
+//  }
+//}
+.swiper-dots {
+	display: flex;
+	position: absolute;
+	left: 50%;
+	transform: translateX(-50%);
+	bottom: 15rpx;
+	z-index: 66;
+
+	.dot {
+		width: 10rpx;
+		height: 10rpx;
+		background: #333333;
+		opacity: 0.3;
+		border-radius: 5rpx;
+		margin: 0 10rpx;
+	}
+
+	.dot-active {
+		width: 20rpx;
+		opacity: 1;
+	}
+}
+
+//.pagination{
+//  display: flex;
+//  justify-content: center;
+//  padding: 20rpx 0;
+//  ::v-deep .swiper-pagination-bullet{
+//    width: 10rpx;
+//    height: 10rpx;
+//    background: #333333;
+//    opacity: 0.3;
+//    border-radius: 5rpx;
+//    margin: 0 5rpx;
+//  }
+//  ::v-deep .swiper-pagination-bullet-active{
+//    width: 20rpx;
+//    height: 10rpx;
+//    opacity: 1;
+//  }
+//}
+
+.btn-more {
+	width: 170rpx;
+	height: 54rpx;
+	line-height: 54rpx;
+	border: 2rpx solid #C5AA7B;
+	color: #C5AA7B;
+	font-size: 24rpx;
+	background-color: transparent;
+	margin: 20rpx auto 0;
+	display: flex;
+	align-items: center;
+}
+</style>

+ 116 - 0
components/canvasShow/basics/product/mixin.js

@@ -0,0 +1,116 @@
+// import { directive, Swiper, SwiperSlide } from 'vue-awesome-swiper'
+// import 'swiper/css/swiper.css'
+import {funMixin} from '../../config/mixin'
+import api from '../../config/api'
+
+export const commonMixin = {
+  name: 'productList',
+  mixins: [funMixin],
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    typeId: {
+      type: Number,
+      default: 1
+    },
+    shopId: {
+      type: Number,
+      default: 0
+    },
+    componentContent: {
+      type: Object
+    }
+  },
+  // components: {
+  //   Swiper,
+  //   SwiperSlide
+  // },
+  // directives: {
+  //   swiper: directive
+  // },
+  data () {
+    return {
+      productData: [1,2,3,4],
+      pageTotal:0,
+      pageSize:8,
+      currentPage:1,
+      loading:true,
+      isFirst:true
+    }
+  },
+  watch: {
+    'componentContent': {
+      handler(newVal, oldVal) {
+		//this.getData()
+      },
+      deep: true
+    }
+  },
+  created() {
+    this.getData(true)
+  },
+  computed: {
+    swiper() {
+      if(this.$refs.mySwiper){
+        return this.$refs.mySwiper.$swiper
+      }
+    }
+  },
+  methods: {
+    getData() {
+      const _ = this
+      // 纵向
+      _.loading=true
+      if (_.componentContent.productData.sourceType === '1') {
+        if(_.componentContent.productData.productIdList && _.componentContent.productData.productIdList.length>0){
+          _.sendReq({
+            url: `${api.getProductsV2}?page=${_.currentPage}&pageSize=${_.pageSize}&ids=${_.componentContent.productData.productIdList}`,
+            method: 'GET'
+          }, (proRes) => {
+            _.productData = [..._.productData,...proRes.data.list].filter(item=>!!item.productId)
+            _.pageTotal = proRes.data.total
+            if(_.isFirst){
+              _.componentContent.productData.imgTextData = _.productData
+            }
+            _.isFirst = false
+            _.loading = false
+          })
+        } else {
+          _.productData = []
+          _.isFirst = false
+          _.loading = false
+        }
+      } else if(_.componentContent.productData.sourceType === '2'){
+        if(_.componentContent.productData.categoryId) {
+          _.sendReq({
+            url: `${api.getProductsV2}?page=1&pageSize=99&classifyId=${_.componentContent.productData.categoryId}`,
+            method: 'GET'
+          }, (proRes) => {
+            _.productData = proRes.data.list
+            _.productData = _.productData.filter(item=>JSON.stringify(item) !== '{}')
+            if(_.isFirst){
+              _.componentContent.productData.imgTextData = _.productData
+            }
+            // _.swiper.update()
+			_.isFirst = false
+			_.loading = false
+          })
+        } else {
+          _.productData = {
+            products:[]
+          }
+		  _.isFirst = false
+		  _.loading = false
+        }
+      }
+    },
+    loadNext(){
+      if(this.loading)return;
+      if(this.productData.length>=this.pageTotal && this.pageTotal!==0)return
+      this.currentPage ++;
+      this.getData()
+    }
+  }
+}

+ 226 - 0
components/canvasShow/basics/product/pc/index.vue

@@ -0,0 +1,226 @@
+<template>
+  <div class="product-list" :class="'terminal'+terminal">
+    <div class="picListWarp" v-if="componentContent.arrangeType == '横向滑动'">
+      <div class="picList" v-if="productData && productData.length>0">
+        <div class="swiper-button-prev"></div>
+        <div class="swiper-button-next"></div>
+        <swiper ref="swiper" class="products-swiper" :options="swiperOption">
+          <swiper-slide class="products-swiper-slide item" v-for="(item,index) in productData" :key="index">
+            <div class="a-link" @click="jumpProductDetail(item)">
+              <div class="itemImgBox">
+                <div class="imgBox">
+                  <el-image
+                      :src="item.image"
+                      fit="contain"></el-image>
+                </div>
+              </div>
+              <div class="text">
+                <h4 class="h4">{{item.productName}}</h4>
+                <div class="priceBox">
+                  <span>¥{{item.price}}</span>
+                  <span class="discount" v-if="item.originalPrice">¥{{item.originalPrice}}</span>
+                </div>
+              </div>
+            </div>
+          </swiper-slide>
+        </swiper>
+      </div>
+    </div>
+    <div v-else class="picList" >
+      <ul class="clearfix" :class="'imgTextNum' +  componentContent.productNum">
+        <li class="item" v-for="(item,index) in productData.slice(0, componentContent.productRowNum * componentContent.productNum)" :key="index">
+          <div class="a-link" @click="jumpProductDetail(item)">
+            <div class="itemImgBox">
+              <div class="imgBox">
+                <el-image
+                    :src="item.image"
+                    fit="contain"></el-image>
+              </div>
+            </div>
+            <div class="text">
+              <h4 class="h4">{{item.productName}}</h4>
+              <div class="priceBox">
+                <span>¥{{item.price}}</span>
+                <span class="discount" v-if="item.originalPrice">¥{{item.originalPrice}}</span>
+              </div>
+            </div>
+          </div>
+        </li>
+      </ul>
+    </div>
+    <button v-show="componentContent.showMore" class="btn-more" @click="jumpProList(componentContent.productData)">查看全部 <span class="icon iconfont icon-arrow-right"></span></button>
+  </div>
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+export default {
+  mixins: [commonMixin],
+  data () {
+    return {
+      swiperOption: {
+        slidesPerView: 4, // 显示数量
+        spaceBetween: 13, // 间隔
+        autoplay: false, // 可选选项,自动滑动
+        loop: true,
+        pagination: {
+          el: '.product-pagination'
+        },
+        navigation: {
+          nextEl: '.swiper-button-next',
+          prevEl: '.swiper-button-prev'
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.product-list{
+  padding: 20px 0;
+  background-color: #fff;
+  .picListWarp{
+    width: 1380px;
+    max-width: 100%;
+    margin: 0 auto;
+    position: relative;
+  }
+  .picList{
+    width: 1200px;
+    max-width: 100%;
+    margin: 0 auto;
+    .swiper-button-prev,.swiper-button-next{
+      width: 50px;
+      height: 50px;
+      position: absolute;
+      cursor:pointer;
+      top: 140px;
+      background-repeat: no-repeat;
+      &:after{
+        content: '';
+      }
+    }
+    .swiper-button-prev{
+      left: 0;
+      background: url('../../../static/images/btn-prev.png');
+    }
+    .swiper-button-next{
+      right: 0;
+      background: url('../../../static/images/btn-next.png');
+    }
+    .a-link{
+      cursor: pointer;
+      &:hover{
+        box-shadow: 3px 4px 20px 0px rgba(186, 186, 186, 0.5);
+      }
+      .itemImgBox {
+        height: auto;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        .imgBox {
+          padding-bottom: 100%;
+          background-color: #cacaca;
+          position: relative;
+          .el-image {
+            width: 100%;
+            height: 100%;
+            position: absolute;
+            top: 0;
+            left: 0;
+          }
+        }
+      }
+      .text{
+        padding:25px 20px 17px;
+        text-align: center;
+        //height: 180px;
+        .h4{
+          font-size: 18px;
+          line-height: 24px;
+          overflow: hidden;
+          text-overflow:ellipsis;
+          white-space: nowrap;
+          color: #333333;
+          //max-height: 48px;
+        }
+        .p{
+          color: #999;
+          font-size: 16px;
+          overflow: hidden;
+          text-overflow:ellipsis;
+          white-space: nowrap;
+          padding-top: 18px;
+          position: relative;
+          margin-top: 8px;
+          &:after{
+            position: absolute;
+            top: 0;
+            left: 50%;
+            margin-left: -80px;
+            width: 160px;
+            height: 2px;
+            background: #F0F0F0;
+            content: '';
+          }
+        }
+        .priceBox {
+          padding-top: 11px;
+          line-height: 33px;
+          span {
+            font-size: 25px;
+            color: #C83732;
+            padding-right: 12px;
+          }
+          span.discount {
+            font-size: 18px;
+            color: #ccc;
+            text-decoration: line-through;
+          }
+        }
+      }
+    }
+    ul{
+      margin: -15px 0 0 -15px;
+      display: flex;
+      flex-wrap: wrap;
+      li{
+        flex: 0 0 50%;
+        padding: 15px 0 0 15px;
+        width: 0;
+      }
+    }
+    .imgTextNum2 {
+      li {
+        flex: 0 0 50%;
+      }
+    }
+    .imgTextNum3 {
+      li {
+        flex: 0 0 33.33%;
+      }
+    }
+    .imgTextNum4 {
+      li {
+        flex: 0 0 25%;
+      }
+    }
+    .imgTextNum5 {
+      li {
+        flex: 0 0 20%;
+      }
+    }
+  }
+}
+.btn-more {
+  width: 130px;
+  height: 41px;
+  border: 2px solid #C5AA7B;
+  color: #C5AA7B;
+  font-size: 18px;
+  background-color: transparent;
+  margin: 20px auto 0;
+  display: block;
+}
+</style>

+ 156 - 0
components/canvasShow/basics/shop.vue

@@ -0,0 +1,156 @@
+<template>
+  <div class="shop"  :class="'terminal' + terminal">
+    <swiper class="swiper" :indicator-dots="false" :autoplay="true" @change="swiperChange">
+      <swiper-item class="shop-item" v-for="(item,index) in imgList" :key="index">
+        <div class="shop-item-warp">
+          <img class="img" :src="item.img" mode="widthFix">
+          <div class="a-link" @click="jumpLink(item.linkObj)">
+            进店逛逛<i class="iconfont icon-arrow-right"></i>
+          </div>
+        </div>
+      </swiper-item>
+    </swiper>
+    <view class="swiper-dots" v-if="imgList && imgList.length">
+      <text class="dot" :class="index === swiperCurrent  && 'dot-active'" v-for="(dot, index) in imgList.length"
+            :key="index"></text>
+    </view>
+  </div>
+</template>
+
+<script>
+// import { directive, Swiper, SwiperSlide } from 'vue-awesome-swiper'
+// import 'swiper/css/swiper.css'
+import {funMixin} from '../config/mixin'
+export default {
+  name: 'shop',
+  mixins: [funMixin],
+  data () {
+    return {
+      swiperCurrent: 0,
+    }
+  },
+  methods:{
+    swiperChange(e) {
+      this.swiperCurrent = e.detail.current;
+    }
+  },
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    componentContent: {
+      type: Object
+    }
+  },
+  // components: {
+  //   Swiper,
+  //   SwiperSlide
+  // },
+  // directives: {
+  //   swiper: directive
+  // },
+  computed: {
+    imgList: function () {
+      return this.componentContent.imgTextData.filter(function (item) {
+        return item.img
+      })
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.shop{
+  position: relative;
+  .swiper{
+    height: 460upx;
+  }
+  &-item{
+    &-warp{
+      position: relative;
+      padding: 0 20upx;
+      .img{
+        width: 100%;
+        height: 420upx;
+      }
+      .a-link{
+        width: 220upx;
+        height: 80upx;
+        line-height: 80upx;
+        background: linear-gradient(225deg, #585858 0%, #333333 100%);
+        box-shadow: 0px 20upx 40upx rgba(0, 0, 0, 0.3);
+        display: block;
+        color: #fff;
+        font-size: 28upx;
+        text-align: center;
+        position: absolute;
+        right: 0;
+        bottom: 30upx;
+        .icon{
+          margin-left: 20upx;
+        }
+      }
+    }
+  }
+  //.pagination{
+  //  display: flex;
+  //  justify-content: center;
+  //  padding:20upx 0;
+  //  ::v-deep .swiper-pagination-bullet{
+  //    width: 12upx;
+  //    height: 12upx;
+  //    background: #333333;
+  //    border-radius: 50%;
+  //    opacity: 0.2;
+  //    margin: 0 10upx;
+  //  }
+  //  ::v-deep .swiper-pagination-bullet-active{
+  //    width: 24upx;
+  //    height: 12upx;
+  //    background: #333333;
+  //    opacity: 1;
+  //    border-radius: 8upx;
+  //  }
+  //}
+  //::v-deep .uni-swiper-dots{
+  //  display: flex;
+  //  justify-content: center;
+  //  padding: 0upx 0;
+  //  .uni-swiper-dot{
+  //    width: 10upx;
+  //    height: 10upx;
+  //    background: #333333;
+  //    opacity: 0.3;
+  //    border-radius: 5upx;
+  //    margin: 0 5upx;
+  //    &-active{
+  //      width: 20upx;
+  //      height: 10upx;
+  //      opacity: 1;
+  //    }
+  //  }
+  //}
+  .swiper-dots {
+    display: flex;
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    bottom: 15rpx;
+    z-index: 66;
+    .dot {
+      width: 10upx;
+      height: 10upx;
+      background: #333333;
+      opacity: 0.3;
+      border-radius: 5upx;
+      margin: 0 10upx;
+    }
+
+    .dot-active {
+      width: 20upx;
+      opacity: 1;
+    }
+  }
+}
+</style>

+ 168 - 0
components/canvasShow/basics/spike/app/index.vue

@@ -0,0 +1,168 @@
+<template>
+  <div class="spike"  v-if="productData.products && productData.products.length>0">
+    <div class="spike-card">
+      <div class="spike-card-top">
+        <h2 class="spike-card-top-title">
+          <!-- #ifdef MP-WEIXIN -->
+          <img class="title-img" src="../../../static/images/spike/img-title.png" alt="秒杀专区" mode="widthFix"/>
+          <!-- #endif -->
+          <!-- #ifdef H5 || APP-PLUS -->
+          <image class="title-img" src="../../../static/images/spike/img-title.png" alt="秒杀专区" mode="widthFix"/>
+          <!-- #endif -->
+        </h2>
+        <div class="spike-card-top-time" v-if="state===2">
+          活动已结束
+        </div>
+        <div class="spike-card-top-time" v-if="state !==2 && count.length">
+          距离{{count[0]}}还有 <div class="span">{{count[1]}}:{{count[2]}}:{{count[3]}}</div>
+        </div>
+        <a class="btn-more" @click="jumpSeckills(productData)">更多<i class="iconfont icon-arrow-right"></i></a>
+      </div>
+      <div class="spike-card-list">
+        <div class="spike-card-item"  v-for='item in productData.products.slice(0,4)' :key='item.productId' @click="jumpProductDetail(item)">
+          <div class="spike-card-item-img">
+            <img :src="item.image" alt="">
+          </div>
+          <div class="spike-card-item-info">
+            <h3 class="name">
+              {{item.productName}}
+            </h3>
+            <div class="stock">
+              还剩{{item.stockNumber}}件
+            </div>
+            <div class="price-warp">
+              <div class="price">
+                ¥ {{item.price}}
+              </div>
+              <!-- <div class="original-price">
+                ¥ {{item.originalPrice}}
+              </div> -->
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+export default {
+  mixins: [commonMixin]
+}
+</script>
+
+<style lang="scss" scoped>
+.spike{
+  background: #F8F8F8;
+  padding: 20upx;
+  &-card{
+    height: 430upx;
+    background: #FFFFFF;
+    border-radius: 20upx;
+    &-top{
+      position: relative;
+      padding: 32upx 0 22upx;
+      display: flex;
+      &-title{
+        padding: 9upx 20upx 9upx 30upx;
+        .title-img{
+          width: 204upx;
+          display: block;
+        }
+      }
+      &-time{
+        padding: 0 18upx;
+        height: 50upx;
+        background: linear-gradient(90deg, #C83732 0%, #E25C44 100%);
+        box-shadow: 0px 6upx 12upx rgba(233, 0, 0, 0.3);
+        opacity: 1;
+        border-radius: 26upx;
+        font-size: 24upx;
+        line-height: 50upx;
+        color: #fff;
+        text-align: center;
+        .span{
+          display: inline;
+        }
+      }
+      .btn-more{
+        position: absolute;
+        right: 8upx;
+        top: 40upx;
+        line-height: 33upx;
+        padding-right: 25upx;
+        font-size: 24upx;
+        color: #333;
+        .iconfont{
+          content: '';
+          font-size: 26upx;
+          position: absolute;
+          right: 0;
+          top: 0;
+        }
+      }
+    }
+    &-list{}
+    &-item{
+      width: 50%;
+      border-top: 1px solid  #F3F4F5;
+      border-left: 1px solid  #F3F4F5;
+      float: left;
+      align-items: center;
+      box-sizing: border-box;
+      &:nth-child(2n+1){
+        border-left: 0px;
+      }
+      &-img{
+        width: 162upx;
+        height: 162upx;
+        margin-right: 10upx;
+        float: left;
+        img {
+          width: 100%;
+          height: 100%;
+          object-fit: contain;
+        }
+      }
+      &-info{
+        height: 100%;
+        margin-left: 172upx;
+        width: 168upx;
+        .name{
+          font-size: 24upx;
+          line-height: 40upx;
+          color: #333333;
+          overflow: hidden;
+          text-overflow:ellipsis;
+          white-space: nowrap;
+        }
+        .stock{
+          padding: 0 8upx;
+          height: 40upx;
+          border: 2upx solid #E4E5E6;
+          line-height: 40upx;
+          margin: 10upx 0;
+          display: inline-block;
+          font-size: 20upx;
+          color: #C5AA7B;
+        }
+        .price{
+          font-size: 32upx;
+          font-weight: bold;
+          line-height: 44upx;
+          color: #C83732;
+          padding-right: 10upx;
+          display: inline-block;
+        }
+        .original-price{
+          font-size: 20upx;
+          line-height: 28upx;
+          color: #CCCCCC;
+          display: inline-block;
+        }
+      }
+    }
+  }
+}
+</style>

+ 140 - 0
components/canvasShow/basics/spike/mixin.js

@@ -0,0 +1,140 @@
+import api from '../../config/api'
+import { funMixin } from '../../config/mixin'
+
+export const commonMixin = {
+  name: 'spikeList',
+  mixins: [funMixin],
+  data() {
+    return {
+      productData: {
+        products: [],
+      },
+      count: [],
+      state: 0,
+      timer: null,
+    }
+  },
+  props: {
+    terminal: {
+      type: Number,
+      default: 4,
+    },
+    typeId: {
+      type: Number,
+      default: 1,
+    },
+    shopId: {
+      type: Number,
+      default: 0,
+    },
+    componentContent: {
+      type: Object,
+    },
+  },
+  watch: {
+    componentContent: {
+      handler(newVal, oldVal) {
+        this.getData()
+      },
+      deep: true,
+    },
+  },
+  created() {
+    this.getData()
+  },
+  methods: {
+    getData() {
+      const _ = this
+      if (_.componentContent.shopSeckillId) {
+        if (this.typeId === 1) {
+          const params = {
+            method: 'GET',
+            url: `${api.getPlatformSeckills}?ids=${_.componentContent.shopSeckillId}`,
+          }
+          this.sendReq(params, (res) => {
+            if (res.data.length > 0) {
+              _.productData = res.data[0]
+              _.productData.products.map(function (value) {
+                value.sliderVal = (
+                  (value.stockNumber / value.total) *
+                  100
+                ).toFixed(2)
+                return value
+              })
+              // 只有进行中和未开始活动, 用倒计时
+              this.timer = setInterval(() => {
+                _.getTime(_.productData)
+              }, 1000)
+            }
+          })
+        }
+        if (this.typeId === 3) {
+          const params = {
+            method: 'GET',
+            url: `${api.getSeckills}?shopId=${_.shopId}&ids=${_.componentContent.shopSeckillId}`,
+          }
+          this.sendReq(params, (res) => {
+            if (res.data.length > 0) {
+              _.productData = res.data[0]
+              _.productData.products.map(function (value) {
+                value.sliderVal = (
+                  (value.stockNumber / value.total) *
+                  100
+                ).toFixed(2)
+                return value
+              })
+              // 只有进行中和未开始活动, 用倒计时
+              if (_.productData.state !== 2) {
+                this.timer = setInterval(() => {
+                  _.getTime(_.productData)
+                }, 1000)
+              }
+            } else {
+              _.productData = {
+                products: [],
+              }
+            }
+          })
+        }
+      } else {
+        _.productData = {
+          products: [],
+        }
+      }
+    },
+    getTime(info) {
+      const date = new Date().getTime()
+      let startTime = ''
+      let endTime = ''
+      if (this.typeId === 1) {
+        startTime = new Date(info.startTime.replace(/-/g, '/')).getTime()
+        endTime = new Date(info.endTime.replace(/-/g, '/')).getTime()
+      } else {
+        startTime = new Date(info.effectiveStart.replace(/-/g, '/')).getTime()
+        endTime = new Date(info.effectiveEnd.replace(/-/g, '/')).getTime()
+      }
+      if (date > endTime) {
+        this.state = 2
+      } else if (startTime > date) {
+        this.state = 0
+        this.countDown(startTime - date) // 未开始
+      } else {
+        this.state = 1
+        this.countDown(endTime - date) // 进行中
+      }
+    },
+
+    countDown(time) {
+      const fn = (v) => (v < 10 ? `0${v}` : v)
+      const t = parseInt(time / 1000)
+      const text = this.state == 0 ? '开始' : '结束'
+      const hour = parseInt(t / 3600)
+      const min = parseInt((t % 3600) / 60)
+      const s = t % 60
+      this.count = [text, fn(hour), fn(min), fn(s)]
+    },
+  },
+  beforeDestroy() {
+    clearInterval(this.timer)
+  },
+}

+ 211 - 0
components/canvasShow/basics/spike/pc/index.vue

@@ -0,0 +1,211 @@
+<template>
+  <div class="spikeList" :class="'terminal'+terminal">
+    <div class="warp" v-if="componentContent.shopSeckillId && productData">
+      <div>
+        <div class="spikeCard">
+          <div class="listLeft">
+            <img src="../../../static/images/spike/bg-spike.png">
+            <div class="bgBox">
+              <div class="title"><img src="../../../static/images/spike/tit-spike.png" alt="秒杀专区"></div>
+              <div class="text">整点秒杀,数量有限</div>
+              <div class="sessions">{{productData.seckillName}} {{isStart && '倒计时'}}</div>
+              <div class="time-text">距离{{count[0]}}还有</div>
+              <div class="timeBox">
+                <span>{{count[1]}}</span><i>:</i><span>{{count[2]}}</span><i>:</i><span>{{count[3]}}</span>
+              </div>
+              <button class="btn-more" @click="jumpSeckills(productData)">查看全部 <span class="icon iconfont icon-arrow-right"></span></button>
+            </div>
+          </div>
+          <div class="listRight">
+            <div class="listItem" v-for='it in productData.products' :key='it.productId' @click="jumpProductDetail(it)">
+              <div class="imgBox">
+                <img :src="it.image" alt="">
+              </div>
+              <div class="itemInfo">
+                <h3>{{it.productName}}</h3>
+                <div class="begrenzt">
+                  <span class="people">还剩{{it.stockNumber}}件</span>
+                  <div class="progress" :style="{'width':it.sliderVal + '%'}"></div>
+                </div>
+                <div class="originalPrice">¥{{it.originalPrice}}</div>
+                <dl class="discountPrice">
+                  <dt>
+                    <img src="../../../static/images/spike/flag-spike.png" alt="秒杀价">
+                  </dt>
+                  <dd>
+                    <span>¥</span><b>{{it.price}}</b>
+                  </dd>
+                </dl>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+export default {
+  mixins: [commonMixin]
+}
+</script>
+
+<style lang="scss" scoped>
+.spikeList{
+  min-height: 100px;
+  &.terminal4{
+    background-color: #F3F4F5;
+    .warp{
+      width: 1200px;
+    }
+    .spikeCard {
+      display: flex;
+      width: 100%;
+      .listLeft {
+        width: 487px;
+        height: 758px;
+        text-align: center;
+        position: relative;
+        .bgBox {
+          position: absolute;
+          top: 0;
+          left: 0;
+          width: 100%;
+          .title{
+            padding-top: 140px;
+          }
+          .text{
+            color: #8E8D8C;
+            font-size: 30px;
+            line-height: 40px;
+            margin: 15px 0 100px 0;
+          }
+          .sessions{
+            color: #FFEBC4;
+            font-size: 30px;
+            line-height: 40px;
+          }
+          .time-text{
+            padding-top: 60px;
+            color: #FFEBC4;
+            font-size: 18px;
+          }
+          .timeBox {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            margin:10px 0 50px;
+            span {
+              display: block;
+              width: 60px;
+              height: 60px;
+              background: rgba(51,51,51,0.8);
+              color: #FFFFFF;
+              font-size: 30px;
+              line-height: 60px;
+            }
+            i {
+              font-size: 30px;
+              color: #fff;
+              font-style: normal;
+              font-weight: bold;
+              padding: 0 13px;
+            }
+          }
+          .btn-more {
+            width: 130px;
+            height: 41px;
+            border: 2px solid #FFEBC4;
+            color: #FFEBC4;
+            font-size: 18px;
+            background-color: transparent;
+          }
+        }
+      }
+      .listRight {
+        padding: 40px 0 0 8px;
+        .listItem {
+          float:left;
+          width: 220px;
+          height: 335px;
+          margin:0 0 15px 15px;
+          background-color: #fff;
+          .imgBox {
+            width: 220px;
+            height: 220px;
+            img {
+              width: 100%;
+              height: 100%;
+            }
+          }
+          .itemInfo {
+            position: relative;
+            padding: 10px;
+            text-align: center;
+            h3{
+              font-size: 14px;
+              color: #333;
+              line-height: 18px;
+              overflow: hidden;
+              text-overflow:ellipsis;
+              white-space: nowrap;
+            }
+            .begrenzt{
+              position: absolute;
+              left: 5px;
+              top: -35px;
+              width: 210px;
+              height: 30px;
+              border: 1px solid #C83732;
+              background-color: #C83732;
+              text-align: center;
+              .people{
+                position: relative;
+                z-index: 2;
+                font-size: 12px;
+                color: #FFBAB8;
+                line-height: 30px;
+              }
+              .progress{
+                position: absolute;
+                right: 0;
+                top: 0;
+                background-color: #fff;
+                height: 28px;
+              }
+            }
+            .discountPrice {
+              display: inline-block;
+              width: 130px;
+              dt{
+                float: left;
+              }
+              dd{
+                border: 1px solid #F3F4F5;
+                color: #C83732;
+                line-height: 30px;
+                font-weight: bold;
+                span {
+                  font-size: 18px;
+                }
+                b {
+                  font-size: 18px;
+                }
+              }
+            }
+            .originalPrice {
+              font-size: 14px;
+              line-height: 19px;
+              padding: 10px 0 6px;
+              color: #ccc;
+              text-decoration: line-through;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 79 - 0
components/canvasShow/basics/text.vue

@@ -0,0 +1,79 @@
+<template>
+  <div class="text warp" :class="['text-'+componentContent.textPos,{'show-more':componentContent.showMore},'terminal' + terminal]" :style="{backgroundColor:componentContent.bgColor}">
+    <div class="line-warp" :class="{'borderBot':componentContent.showLine}">
+      <h3 class="h3" :style="{fontSize:componentContent.fontSizeNum+'px',fontWeight:componentContent.textFontW,color:componentContent.titColor}">{{componentContent.title}}</h3>
+      <p class="p" :style="{fontSize:componentContent.describeSizeNum+'px',fontWeight:componentContent.describeFontW,color:componentContent.describeColor}">{{componentContent.describe}}</p>
+      <div class="btn-more" v-show="componentContent.showMore" :class="'style'+componentContent.styleValue" @click="jumpLink(item.linkObj)"><span>查看更多</span><i class="iconfont icon-arrow-right"></i></div>
+    </div>
+  </div>
+</template>
+
+<script>
+  export default {
+    name: 'textComponent',
+    data () {
+      return {
+      }
+    },
+    props: {
+      terminal: {
+        type: Number,
+        default: 4
+      },
+      componentContent: {
+        type: Object
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .text{
+    padding: 0 20upx;
+    position: relative;
+    .line-warp{
+      padding: 10upx 0;
+    }
+    .borderBot{
+      border-bottom: 1upx solid #cccc;
+    }
+    .h3{
+      line-height: 1.5;
+    }
+    .p{
+      line-height: 1.5;
+      margin-top: 5upx;
+    }
+    .style1{
+
+    }
+    .style2{
+      .iconfont{
+        display: none;
+      }
+    }
+    .style3{
+      span{
+        display: none;
+      }
+    }
+    &.text-left{
+      text-align: left;
+      &.show-more{
+        position: relative;
+        padding-right: 20upx;
+        .btn-more{
+          position: absolute;
+          right: 0;
+          top: 10upx;
+        }
+      }
+    }
+    &.text-center{
+      text-align: center;
+    }
+    &.text-right{
+      text-align: right;
+    }
+  }
+</style>

+ 125 - 0
components/canvasShow/basics/video.vue

@@ -0,0 +1,125 @@
+<template>
+  <div class="videoBox warp" :class="'terminal' + terminal">
+    <div class="videoLeftBox">
+      <h3>{{componentContent.title}}</h3>
+      <div v-html="componentContent.mainBody"></div>
+    </div>
+    <div class="videoRightBox" v-if="componentContent.coverImg && isPlay">
+      <video class="myVideo" id="myVideo" :src="componentContent.videoUrl" enable-danmu danmu-btn controls></video>
+    </div>
+    <div class="videoCoverBox" v-if="componentContent.coverImg && !isPlay" @click="handlePlayVideo">
+      <image :src="componentContent.coverImg" />
+    </div>
+    <div class="videoRightBox" v-if="!componentContent.coverImg">
+      <video class="myVideo" id="myVideo" :src="componentContent.videoUrl" enable-danmu danmu-btn controls></video>
+    </div>
+    <div class="clear"></div>
+<!--    <div style="width:100vw;height:200px">
+      <image :src="componentContent.coverImg" />
+    </div>-->
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'videoBox',
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    componentContent: {
+      type: Object
+    }
+  },
+  created(){
+  },
+  mounted() {
+    this.videoContext = uni.createVideoContext('myVideo',this)
+  },
+  data () {
+    return {
+      isPlay:false,
+      videoContext:null
+    }
+  },
+  methods:{
+    handlePlayVideo(){
+      this.isPlay = true
+      setTimeout(()=>{
+        this.videoContext.play()
+      },500)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+ .videoBox {
+   padding: 20upx 0;
+   display: flex;
+   justify-content: flex-start;
+   align-items: center;
+   .videoLeftBox {
+     width: 50%;
+     padding-right: 10%;
+     h3 {
+       font-size: 28upx;
+       color: #333333;
+       margin-bottom: 30upx;
+     }
+     p {
+       color: #333333;
+       font-size: 14upx;
+       line-height: 30upx;
+     }
+   }
+   .videoRightBox {
+     width: 50%;
+     video {
+       width: 100%;
+     }
+   }
+   .clear {
+     clear: both;
+   }
+ }
+ .terminal1,.terminal2,.terminal3{
+   &.videoBox{
+     display: block;
+     .videoLeftBox{
+       width: 100%;
+       text-align: center;
+       margin-bottom: 20upx;
+     }
+     .videoRightBox {
+       width: 100vw;
+       height: 56.25vw; //16:9
+     }
+   }
+ }
+ .videoCoverBox{
+   width: 100vw;
+   height: 56.25vw; //16:9
+
+   position: relative;
+   &:before{
+     content: '';
+     width: 0rpx;
+     height: 0rpx;
+     border-left:80rpx solid #fff;
+     border-right:50rpx solid transparent;
+     border-top:50rpx solid transparent;
+     border-bottom:50rpx solid transparent;
+    position: absolute;
+     top: 50%;
+     left: 50%;
+     transform: translate(-30%,-50%);
+     z-index: 99;
+   }
+   image{
+     width: 100%;
+     height: 100%;
+   }
+ }
+</style>

+ 319 - 0
components/canvasShow/basics/vip/app/index.vue

@@ -0,0 +1,319 @@
+<template>
+  <div class="vip">
+    <div class="vip-card">
+      <div class="vip-title">
+        <!-- #ifdef MP-WEIXIN -->
+        <img class="title-img" src="../../../static/images/vip/img-title.png" alt="会员专区" mode="widthFix"/>
+        <!-- #endif -->
+        <!-- #ifdef H5 || APP-PLUS -->
+        <image class="title-img" src="../../../static/images/vip/img-title.png" alt="会员专区" mode="widthFix"/>
+        <!-- #endif -->
+        <a v-show="componentContent.showMore" class="btn-more a-link" @click="jumpVip">更多<i class="iconfont icon-arrow-right"></i></a>
+      </div>
+      <div>
+        <div v-if="productData.length > 2">
+          <swiper class="swiper vip-list" :circular="true" :indicator-dots="false" :autoplay="true" @change="swiperChange">
+            <swiper-item class="vip-item-warp" v-for="(itemJ,indexJ) in listTemp" :key="indexJ">
+              <div class="vip-item"  v-for="(item,index) in itemJ" :key="index"  @click="jumpProductDetail(item)">
+                <div class="vip-item-img">
+                  <image class="img default-img" :src="item.image"></image>
+                </div>
+                <div class="vip-item-info">
+                  <h3 class="name">
+                    {{item.productName}}
+                  </h3>
+                  <div class="stock">
+                    还剩{{item.stockNumber}}件
+                  </div>
+                  <div class="original-price">
+                    ¥ {{item.originalPrice}}
+                  </div>
+                  <div class="price-warp">
+                    <div class="flag">
+                      <!-- #ifdef MP-WEIXIN -->
+                      <img src="../../../static/images/vip/flag-vip.png" alt="会员价" class="flagImg"/>
+                      <!-- #endif -->
+                      <!-- #ifdef H5 || APP-PLUS -->
+                      <image class="flagImg" src="../../../static/images/vip/flag-vip.png" alt="会员专区" mode="widthFix"/>
+                      <!-- #endif -->
+                    </div>
+                    <div class="price">
+                      ¥ {{item.price}}
+                    </div>
+                  </div>
+                  <div class="btn-buy">
+                    <span>去购买</span>
+                    <div class="progress">
+                      <i></i>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </swiper-item>
+          </swiper>
+          <view class="swiper-dots" v-if="listTemp && listTemp.length > 1">
+            <text class="dot" :class="swiperCurrent === index && 'dot-active'" v-for="(dot, index) in listTemp.length"
+                  :key="index"></text>
+          </view>
+        </div>
+        <div class="swiper vip-list" v-else>
+          <div class="vip-item-warp" v-for="(itemJ,indexJ) in listTemp" :key="indexJ">
+            <div class="vip-item"  v-for="(item,index) in itemJ" :key="index" @click="jumpProductDetail(item)">
+              <div class="vip-item-img">
+                <img class="img default-img" :src="item.image">
+              </div>
+              <div class="vip-item-info">
+                <h3 class="name">
+                  {{item.productName}}
+                </h3>
+                <div class="stock">
+                  还剩{{item.stockNumber}}件
+                </div>
+                <div class="original-price">
+                  ¥ {{item.originalPrice}}
+                </div>
+                <div class="price-warp">
+                  <div class="flag">
+                    <!-- #ifdef MP-WEIXIN -->
+                    <img src="../../../static/images/vip/flag-vip.png" alt="会员价" class="flagImg"/>
+                    <!-- #endif -->
+                    <!-- #ifdef H5 || APP-PLUS -->
+                    <image class="flagImg" src="../../../static/images/vip/flag-vip.png" alt="会员专区" mode="widthFix"/>
+                    <!-- #endif -->
+                  </div>
+                  <div class="price">
+                    ¥ {{item.price}}
+                  </div>
+                </div>
+                <div class="btn-buy">
+                  <span>去购买</span>
+                  <div class="progress">
+                    <i></i>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+<!--      <div class="pagination vip-pagination"></div>-->
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+export default {
+  mixins: [commonMixin],
+  data () {
+    return {
+      swiperCurrent: 0,
+    }
+  },
+  computed: {
+    listTemp: function () {
+      var list = this.productData;
+      var arrTemp = [];
+      var index = -1;
+      var count = 2; // 每组多少条
+      for (var i = 0; i < list.length; i++) {
+        if (i % count==0) {
+          arrTemp.push([]);
+          index ++
+        }
+        arrTemp[index].push(list[i]);
+      }
+      return arrTemp;
+    }
+  },
+  methods:{
+    swiperChange(e) {
+      this.swiperCurrent = e.detail.current;
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.vip{
+  &-card{
+    background: #333333;
+    padding: 0 20upx 20upx;
+    position: relative;
+  }
+  &-title{
+    padding: 42upx 0 28upx 30upx;
+    position: relative;
+    .title-img{
+      display: block;
+      width: 197upx;
+    }
+    .btn-more{
+      position: absolute;
+      right: 8upx;
+      top: 40upx;
+      line-height: 33upx;
+      padding-right: 25upx;
+      font-size: 24upx;
+      color: #FFEBC4;
+      .iconfont{
+        content: '';
+        font-size: 26upx;
+        position: absolute;
+        right: 0;
+        top: 0;
+      }
+    }
+  }
+  &-list{
+    height: 562upx;
+  }
+  &-item{
+    display: flex;
+    background-color: #fff;
+    margin-top: 20upx;
+    &:first-child{
+      margin-top: 0upx;
+    }
+    &-img{
+      width: 260upx;
+      height: 260upx;
+      margin-right: 20upx;
+      background-color: #ececec;
+      .img {
+        width: 100%;
+        height: 100%;
+        object-fit: contain;
+        display: block;
+      }
+    }
+    &-info{
+      flex: 1;
+      position: relative;
+      .name{
+        font-size: 28upx;
+        //height: 75rpx;
+        line-height: 40upx;
+        color: #333333;
+        margin: 30upx 0 10upx;
+        overflow:hidden;
+        text-overflow:ellipsis;
+        display:-webkit-box;
+        -webkit-box-orient:vertical;
+        -webkit-line-clamp:1;
+      }
+      // #ifdef H5 || APP-PLUS
+      .name{
+        height: 40rpx;
+      }
+      // #endif
+      .stock{
+        color: #C5AA7B;
+        font-size: 20upx;
+        border: 2upx solid #E4E5E6;
+        line-height: 40upx;
+        padding: 0 5upx;
+        display: inline-block;
+        margin-bottom: 30upx;
+      }
+      .original-price{
+        font-size: 24upx;
+        line-height: 34upx;
+        color: #CCCCCC;
+        text-decoration: line-through;
+      }
+      .flag{
+        float: left;
+        padding: 12upx 10upx 0 0;
+        .flagImg {
+          width: 58upx;
+          height: 36upx;
+          display: block;
+        }
+      }
+      .price{
+        font-size: 40upx;
+        font-weight: bold;
+        line-height: 56upx;
+        color: #C83732;
+      }
+      .btn-buy{
+        position: absolute;
+        right: 20upx;
+        bottom: 20upx;
+        width: 160upx;
+        height: 84upx;
+        background: linear-gradient(90deg, #C83732 0%, #E25C44 100%);
+        box-shadow: 0px 6upx 12upx rgba(233, 0, 0, 0.3);
+        border-radius: 10upx;
+        text-align: center;
+        padding: 16upx 20upx 0;
+        span{
+          font-size: 24upx;
+          line-height: 34upx;
+          color: #FFFFFF;
+          margin-bottom: 10upx;
+          display: block;
+        }
+        .progress{
+          height: 8upx;
+          background: rgba(255,255,255,0.5);
+          border-radius: 4upx;
+          position: relative;
+          i{
+            position: absolute;
+            left: 0;
+            top: 0;
+            width: 50%;
+            height: 8upx;
+            background-color: #fff;
+            border-radius: 4upx;
+          }
+        }
+      }
+    }
+  }
+.vip-item-info {
+  .price-warp {
+    display: flex;
+    align-items: center;
+  }
+}
+  .swiper-dots {
+    display: flex;
+    position: absolute;
+    left: 50%;
+    transform: translateX(-50%);
+    bottom: 15upx;
+    z-index: 66;
+    .dot {
+      width: 24upx;
+      height: 4upx;
+      background: #fff;
+      opacity: 0.5;
+      border-radius: 2upx;
+      margin: 0 10upx;
+    }
+
+    .dot-active {
+      opacity: 1;
+    }
+  }
+  //.pagination{
+  //  display: flex;
+  //  justify-content: center;
+  //  padding-top:20upx;
+  //  ::v-deep .swiper-pagination-bullet{
+  //    width: 24upx;
+  //    height: 4upx;
+  //    background: #fff;
+  //    opacity: 0.5;
+  //    border-radius: 2upx;
+  //    margin: 0 5upx;
+  //  }
+  //  ::v-deep .swiper-pagination-bullet-active{
+  //    opacity: 1;
+  //  }
+  //}
+}
+</style>

+ 60 - 0
components/canvasShow/basics/vip/mixin.js

@@ -0,0 +1,60 @@
+// import { directive, Swiper, SwiperSlide } from 'vue-awesome-swiper'
+// import 'swiper/css/swiper.css'
+import api from '../../config/api'
+import {funMixin} from '../../config/mixin'
+
+export const commonMixin = {
+  name: 'productList',
+  mixins: [funMixin],
+  props: {
+    terminal: {
+      type: Number,
+      default: 4
+    },
+    typeId: {
+      type: Number,
+      default: 1
+    },
+    shopId: {
+      type: Number,
+      default: 0
+    },
+    componentContent: {
+      type: Object
+    }
+  },
+  // components: {
+  //   Swiper,
+  //   SwiperSlide
+  // },
+  // directives: {
+  //   swiper: directive
+  // },
+  data () {
+    return {
+      productData: []
+    }
+  },
+  watch: {
+    'componentContent': {
+      handler(newVal, oldVal) {
+        this.getData()
+      },
+      deep: true
+    }
+  },
+  created() {
+    this.getData()
+  },
+  methods: {
+    getData() {
+      const _ = this
+      _.sendReq({
+        url: `${api.getMemberProducts}?page=1&pageSize=20`,
+        method: 'GET'
+      }, (proRes) => {
+        _.productData = proRes.data.list
+      })
+    }
+  }
+}

+ 241 - 0
components/canvasShow/basics/vip/pc/index.vue

@@ -0,0 +1,241 @@
+<template>
+  <div class="product-list" :class="'terminal'+terminal">
+    <div class="picListWarp" v-if="componentContent.arrangeType == '横向滑动'">
+      <div class="picList" v-if="productData && productData.length>0">
+        <div class="vip-button-prev"></div>
+        <div class="vip-button-next"></div>
+        <swiper class="products-swiper" :options="swiperOption">
+          <swiper-slide class="products-swiper-slide item" v-for="(item,index) in productData" :key="index">
+            <div class="a-link" @click="jumpProductDetail(item)">
+              <div class="itemImgBox">
+                <div class="imgBox">
+                  <el-image
+                    :src="item.image"
+                    fit="contain"></el-image>
+                </div>
+              </div>
+              <div class="text">
+                <h4 class="h4">{{item.productName}}</h4>
+                <div class="priceBox">
+                  <dl>
+                    <dt><img src="../../../static/images/vip/flag-vip.png" alt="会员价"></dt>
+                    <dd>
+                      ¥{{item.price}}
+                    </dd>
+                  </dl>
+                </div>
+              </div>
+            </div>
+          </swiper-slide>
+        </swiper>
+      </div>
+    </div>
+    <div v-else class="picList" >
+      <ul class="clearfix" :class="'imgTextNum' +  componentContent.productNum">
+        <li class="item" v-for="(item,index) in productData.slice(0, componentContent.productRowNum * componentContent.productNum)" :key="index">
+          <div class="a-link" @click="jumpProductDetail(item)">
+            <div class="itemImgBox">
+              <div class="imgBox">
+                <el-image
+                  :src="item.image"
+                  fit="contain"></el-image>
+              </div>
+            </div>
+            <div class="text">
+              <h4 class="h4">{{item.productName}}</h4>
+              <div class="priceBox">
+                <dl>
+                  <dt><img src="../../../static/images/vip/flag-vip.png" alt="会员价"></dt>
+                  <dd>
+                    ¥{{item.price}}
+                  </dd>
+                </dl>
+              </div>
+            </div>
+          </div>
+        </li>
+      </ul>
+    </div>
+    <button v-show="componentContent.showMore" class="btn-more" @click="jumpVip">查看全部 <span class="icon iconfont icon-arrow-right"></span></button>
+  </div>
+</template>
+
+<script>
+import {commonMixin} from '../mixin'
+  export default {
+    mixins: [commonMixin],
+    data () {
+      return {
+        swiperOption: {
+          slidesPerView: 4, // 显示数量
+          spaceBetween: 13, // 间隔
+          autoplay: false, // 可选选项,自动滑动
+          loop: true,
+          pagination: {
+            el: '.vip-pagination'
+          },
+          navigation: {
+            nextEl: '.vip-button-next',
+            prevEl: '.vip-button-prev'
+          }
+        }
+      }
+    }
+  }
+</script>
+
+<style lang="scss" scoped>
+  .product-list{
+    padding: 20px 0;
+    background-color: #fff;
+    .picListWarp{
+      width: 1380px;
+      max-width: 100%;
+      margin: 0 auto;
+      position: relative;
+    }
+    .picList{
+      width: 1200px;
+      max-width: 100%;
+      margin: 0 auto;
+      .vip-button-prev,.vip-button-next{
+        width: 50px;
+        height: 50px;
+        position: absolute;
+        cursor:pointer;
+        top: 140px;
+        background-repeat: no-repeat;
+        &:after{
+          content: '';
+        }
+      }
+      .vip-button-prev{
+        left: 0;
+        background: url('../../../static/images/btn-prev.png');
+      }
+      .vip-button-next{
+        right: 0;
+        background: url('../../../static/images/btn-next.png');
+      }
+      .a-link{
+        cursor: pointer;
+        &:hover{
+          box-shadow: 3px 4px 20px 0px rgba(186, 186, 186, 0.5);
+        }
+        .itemImgBox {
+          height: auto;
+          display: flex;
+          flex-direction: column;
+          justify-content: center;
+          .imgBox {
+            padding-bottom: 100%;
+            background-color: #cacaca;
+            position: relative;
+            .el-image {
+              width: 100%;
+              height: 100%;
+              position: absolute;
+              top: 0;
+              left: 0;
+            }
+          }
+        }
+        .text{
+          padding:25px 20px 17px;
+          text-align: center;
+          //height: 180px;
+          .h4{
+            font-size: 18px;
+            line-height: 24px;
+            overflow: hidden;
+            text-overflow:ellipsis;
+            white-space: nowrap;
+            color: #333333;
+            margin-bottom: 15px;
+            //max-height: 48px;
+          }
+          .p{
+            color: #999;
+            font-size: 16px;
+            overflow: hidden;
+            text-overflow:ellipsis;
+            white-space: nowrap;
+            padding-top: 18px;
+            position: relative;
+            margin-top: 8px;
+            &:after{
+              position: absolute;
+              top: 0;
+              left: 50%;
+              margin-left: -80px;
+              width: 160px;
+              height: 2px;
+              background: #F0F0F0;
+              content: '';
+            }
+          }
+          .priceBox {
+            dl {
+              display: inline-block;
+              min-width: 130px;
+              dt{
+                float: left;
+                img{
+                  display: block;
+                }
+              }
+              dd{
+                border: 1px solid #F3F4F5;
+                font-size: 25px;
+                line-height: 34px;
+                color: #C83732;
+                margin-left: 57px;
+                padding: 0 10px;
+              }
+            }
+          }
+        }
+      }
+      ul{
+        margin: -15px 0 0 -15px;
+        display: flex;
+        flex-wrap: wrap;
+        li{
+          flex: 0 0 50%;
+          padding: 15px 0 0 15px;
+          width: 0;
+        }
+      }
+      .imgTextNum2 {
+        li {
+          flex: 0 0 50%;
+        }
+      }
+      .imgTextNum3 {
+        li {
+          flex: 0 0 33.33%;
+        }
+      }
+      .imgTextNum4 {
+        li {
+          flex: 0 0 25%;
+        }
+      }
+      .imgTextNum5 {
+        li {
+          flex: 0 0 20%;
+        }
+      }
+    }
+  }
+  .btn-more {
+    width: 130px;
+    height: 41px;
+    border: 2px solid #C5AA7B;
+    color: #C5AA7B;
+    font-size: 18px;
+    background-color: transparent;
+    margin: 20px auto 0;
+    display: block;
+  }
+</style>

+ 204 - 0
components/canvasShow/canvasShowPage.vue

@@ -0,0 +1,204 @@
+<template>
+	<div class="layout hom-layout">
+		<div v-for="(item, index) in componentsData" :key="index" class="list-group-item">
+			<!-- {{ item.type }} -->
+			<!--        <component :is="componentMap[terminal-1].get(item.type)" :componentContent="item.componentContent" :terminal="terminal" :typeId="typeId" :shopId="shopId"></component> -->
+			<ComBanner
+				v-if="item.type === 'banner'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComBanner>
+			<ComText
+				v-if="item.type === 'text'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComText>
+			<ComImageText
+				v-if="item.type === 'imageText'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComImageText>
+			<ComBrandList
+				v-if="item.type === 'brandList'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComBrandList>
+			<ComCategoryList
+				v-if="item.type === 'categoryList'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComCategoryList>
+			<ComImageTextList
+				v-if="item.type === 'imageTextList'" :component-content="item.componentContent"
+				:terminal="terminal" :type-id="typeId" :shop-id="shopId"
+			></ComImageTextList>
+			<ComAssistDiv
+				v-if="item.type === 'assistDiv'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComAssistDiv>
+			<ComImageTextNav
+				v-if="item.type === 'imageTextNav'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComImageTextNav>
+			<ComProduct
+				v-if="item.type === 'productList'" ref="productPage" :component-content="item.componentContent"
+				:terminal="terminal" :type-id="typeId" :shop-id="shopId"
+			></ComProduct>
+			<ComVideoBox
+				v-if="item.type === 'videoBox'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComVideoBox>
+			<ComCoupon
+				v-if="item.type === 'coupon'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComCoupon>
+			<ComCustom
+				v-if="item.type === 'custom'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComCustom>
+			<ComNotice
+				v-if="item.type === 'notice'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComNotice>
+			<ComVip
+				v-if="item.type === 'vip'" :component-content="item.componentContent" :terminal="terminal" :type-id="typeId"
+				:shop-id="shopId"
+			></ComVip>
+			<ComGroup
+				v-if="item.type === 'groupList'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComGroup>
+			<ComDiscount
+				v-if="item.type === 'discountList'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComDiscount>
+			<ComSpike
+				v-if="item.type === 'spikeList'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComSpike>
+			<ComPrice
+				v-if="item.type === 'priceList'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComPrice>
+			<ComNewProduct
+				v-if="item.type === 'newProduct'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComNewProduct>
+			<ComShop
+				v-if="item.type === 'shop'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComShop>
+			<ComLive
+				v-if="item.type === 'live'" :component-content="item.componentContent" :terminal="terminal"
+				:type-id="typeId" :shop-id="shopId"
+			></ComLive>
+		</div>
+	</div>
+</template>
+
+<script>
+// import comComponentMap from './componentMap'
+
+import comBanner from '@/components/canvasShow/basics/banner'
+import comText from '@/components/canvasShow/basics/text'
+import comImageText from '@/components/canvasShow/basics/imageText'
+import comBrandList from '@/components/canvasShow/basics/brandList'
+import comCategoryList from '@/components/canvasShow/basics/categoryList'
+import comImageTextList from '@/components/canvasShow/basics/imageTextList'
+import comAssistDiv from '@/components/canvasShow/basics/assistDiv'
+import comImageTextNav from '@/components/canvasShow/basics/imageTextNav'
+import comProduct from '@/components/canvasShow/basics/product/app/index'
+import comVideoBox from '@/components/canvasShow/basics/video'
+import comCoupon from '@/components/canvasShow/basics/coupon/app'
+import comCustom from '@/components/canvasShow/basics/custom'
+import comNotice from '@/components/canvasShow/basics/notice'
+import comVip from '@/components/canvasShow/basics/vip/app'
+import comGroup from '@/components/canvasShow/basics/group/app/index'
+import comDiscount from '@/components/canvasShow/basics/discount/app'
+import comSpike from '@/components/canvasShow/basics/spike/app'
+import comPrice from '@/components/canvasShow/basics/price/app'
+import comNewProduct from '@/components/canvasShow/basics/newProduct/app'
+import comShop from '@/components/canvasShow/basics/shop'
+import comLive from '@/components/canvasShow/basics/live/app'
+import { sendReqMixin } from './config/mixin'
+
+export default {
+	name: 'CanvasPage',
+	components: {
+		ComBanner: comBanner,
+		ComText: comText,
+		ComImageText: comImageText,
+		ComBrandList: comBrandList,
+		ComCategoryList: comCategoryList,
+		ComImageTextList: comImageTextList,
+		ComAssistDiv: comAssistDiv,
+		ComImageTextNav: comImageTextNav,
+		ComProduct: comProduct,
+		ComVideoBox: comVideoBox,
+		ComCoupon: comCoupon,
+		ComCustom: comCustom,
+		ComGroup: comGroup,
+		ComDiscount: comDiscount,
+		ComSpike: comSpike,
+		ComPrice: comPrice,
+		ComNotice: comNotice,
+		ComVip: comVip,
+		ComNewProduct: comNewProduct,
+		ComShop: comShop,
+		ComLive: comLive
+	},
+	mixins: [ sendReqMixin ],
+	props: {
+		terminal: {
+			type: Number,
+			default: 4
+		},
+		typeId: {
+			type: Number,
+			default: 1
+		},
+		shopId: {
+			type: Number,
+			default: 0
+		},
+		componentsData: {
+			type: Array,
+			default: () => []
+		}
+	},
+	data() {
+		return {
+			// terminal: 4, // 画布设备 1 小程序,2 H5,3 App 4 电脑
+			// typeId: 3, // 画布类型 1 平台画布,2 自定义页面,3 商家店铺装修
+			// shopId: 0, // 店铺id
+			// componentsData: [],
+			// componentMap: componentMap
+		}
+	},
+	methods: {
+
+	}
+}
+</script>
+
+<style lang="scss"
+       scoped
+>
+.hom-layout {
+	background-color: #ffffff;
+	width: 100%;
+	overflow: hidden;
+}
+</style>
+
+<style lang="scss">
+.warp {
+	width: 710upx;
+	margin: 0 auto;
+	max-width: 100%;
+
+	&.terminal4 {
+		width: 1200px;
+		max-width: 100%;
+	}
+}
+
+.flex-box {
+	display: flex;
+}
+</style>

+ 32 - 0
components/canvasShow/config/api.js

@@ -0,0 +1,32 @@
+// 导入api接口模块
+
+// 获取当前环境变量 true => 生产环境 false => 开发环境
+// const BASEURL = process.env.VUE_APP_DOMAIN_PREFIX
+const BASEURL = process.env.NODE_ENV === 'production' ? 'https://nsappapi.tuanfengkeji.cn' : 'http://192.168.0.91:9007'
+// const BASEURL = (process.env.NODE_ENV === 'production') ? 'http://127.0.0.1:9007' : 'http://127.0.0.1:9007'
+
+export const api = {
+	// 画布模块
+	fileUpload: BASEURL + '/file/upload', // 文件上传
+	getClassify: BASEURL + '/canvas/getClassify', // 查询分类层级
+	getProducts: BASEURL + '/canvas/getProducts', // 选择商品查询
+	getProductsV2: BASEURL + '/canvas/getProducts2', // 选择商品查询V2(2023.3.9 优化版本接口)
+	saveCanvas: BASEURL + '/canvas/saveCanvas', // 保存画布
+	getCanvas: BASEURL + '/canvas/getCanvas', // 读取画布
+	getShops: BASEURL + '/canvas/getShops', // 选择店铺查询
+	getCoupons: BASEURL + '/canvas/getCoupons', // 优惠券查询
+	getShopCoupons: BASEURL + '/canvas/getShopCoupons', // 优惠券查询
+	takeCoupon: BASEURL + '/coupon/takeCoupon', // 领取优惠券
+	selectCanvasCustomList: BASEURL + '/canvas/selectCanvasCustomList', // 自定义页面查询
+	getPlatformSeckills: `${BASEURL}/canvas/getPlatformSeckills`, // 平台秒杀活动
+	getSeckills: `${BASEURL}/renovation/getSeckills`, // 商家秒杀活动w
+	getMinDiscount: `${BASEURL}/canvas/getMinDiscount`, // 平台限时折扣
+	getDiscounts: `${BASEURL}/renovation/getDiscounts`, // 商家限时折扣
+	getAdminGroupWorks: `${BASEURL}/canvas/getGroupWorks`, // 平台拼团专区
+	getGroupWorks: `${BASEURL}/renovation/getGroupWorks`, // 商家拼团专区
+	getPriceProducts: `${BASEURL}/canvas/getPriceProducts`, // 商家定价捆绑
+	getPrices: `${BASEURL}/canvas/getPrices`, // 商家定价捆绑
+	getMemberProducts: `${BASEURL}/canvas/getMemberProducts`, // 查询会员商品数据
+	getNotices: `${BASEURL}/canvas/getNotices` // 平台获取公告数据
+}
+export default api

+ 13 - 0
components/canvasShow/config/config.js

@@ -0,0 +1,13 @@
+// 画布配置
+// import Cookies from 'js-cookie'
+import { J_STORAGE_KEY } from '../../../config/constant'
+
+const config = {
+	// terminal: 4, // 画布设备 1 小程序,2 H5,3 App 4 电脑
+	typeId: 0, // 页面类型 0 C端 1 平台画布,2 自定义页面,3 商家店铺装修
+	getToken() {
+		return uni.getStorageSync(J_STORAGE_KEY).token
+	}
+}
+
+export default config

+ 201 - 0
components/canvasShow/config/mixin/funMixin.js

@@ -0,0 +1,201 @@
+// import router from '@/router'
+import api from '../api'
+import {sendReq} from './sendReqMixin'
+import { mapMutations } from 'vuex'
+import canvasConfig from '../config'
+/*
+ * 公共方法的 mixin
+ */
+export const tool = {
+  mixins: [sendReq],
+  props: {
+    isNoData: {
+      type: Boolean,
+      default: false
+    },
+    comType: {
+      type: String,
+      default: ''
+    }
+  },
+  mounted() {
+  },
+  methods: {
+    ...mapMutations({
+      setCurrentPro: 'SET_CURRENTPRO'
+    }),
+    // 判断url
+    jumpLink (linkObj) {
+      var link = ''
+      if(linkObj && linkObj.typeText && linkObj.data){
+        switch (linkObj.typeText) {
+          case '类别':
+            this.jumpCategory(linkObj.data)
+            break
+          case '店辅':
+            this.jumpStore(linkObj.data)
+            break
+          case '商品':
+            this.jumpProductDetail(linkObj.data)
+            break
+          case '自定义':
+            // router.push("/category");
+          case '公告':
+            this.jumpNoticeDetail(linkObj.data)
+            break
+        }
+      } else if(linkObj.selsectValue==='/index'){
+        uni.navigateTo({
+          url: `/pages/index/index`
+        })
+      }
+      return link
+    },
+    // 跳转到类别主页
+    jumpCategory(item){
+      uni.navigateTo({
+        url: `/pages_category_page1/goodsModule/goodsList?category3Id=${item.id}`
+      })
+    },
+    // 跳转到产品列表
+    jumpProList(item){
+      if(item.sourceType === '1'){
+        uni.navigateTo({
+          url: `/pages_category_page1/goodsModule/canvasGoods?sourceType=${item.sourceType}&ids=${item.productIdList}`
+        })
+      } else if(item.sourceType === '2'){
+        uni.navigateTo({
+          url: `/pages_category_page1/goodsModule/canvasGoods?sourceType=${item.sourceType}&classifyId=${item.categoryId}`
+        })
+      }
+    },
+    // 跳转到店铺主页
+    jumpStore(item){
+      uni.navigateTo({
+        url: `/pages_category_page1/store/index?storeId=${item.shopId}`
+      })
+    },
+    // 跳转到商品详情
+    jumpProductDetail(item){
+      uni.navigateTo({
+        url: '/pages_category_page1/goodsModule/goodsDetails?shopId=' + item.shopId + '&productId=' + item.productId + '&skuId=' + item
+            .skuId
+      })
+    },
+    // 跳转到秒杀专区
+    jumpSeckills(item){
+      if(item.shopId){
+        uni.navigateTo({
+          url: '/pages_category_page1/discount/spikeList?shopId=' + item.shopId + '&shopSeckillId=' + item.shopSeckillId
+        })
+      } else {
+        uni.navigateTo({
+          url: '/pages_category_page1/discount/spikeList'
+        })
+      }
+    },
+    // 跳转到拼团专区
+    jumpGroupWorks(item){
+      if(item.shopId){
+        uni.navigateTo({
+          url: '/pages_category_page1/discount/groupBuy?shopId=' + item.shopId + '&shopGroupWorkId=' + item.shopGroupWorkId
+        })
+      } else {
+        uni.navigateTo({
+          url: '/pages_category_page1/discount/groupBuy?'
+        })
+      }
+    },
+    // 跳转到折扣专区
+    jumpDiscount(item){
+      if(item.shopId){
+        if (item.shopDiscountId) {
+          uni.navigateTo({
+            url: '/pages_category_page1/discount/discount?shopId=' + item.shopId + '&shopDiscountId=' + item.shopDiscountId
+          })
+        } else {
+          uni.showToast({
+            title: '暂无活动',
+            icon: "none"
+          });
+        }
+      } else {
+        if (item.discountId) {
+          uni.navigateTo({
+            url: '/pages_category_page1/discount/platformDiscount?discountId=' + item.discountId
+          })
+        } else {
+          uni.showToast({
+            title: '暂无活动',
+            icon: "none"
+          });
+        }
+      }
+    },
+    // 跳转到会员专区
+    jumpVip(){
+      uni.navigateTo({
+        url: '/pages_category_page1/memberCenter/activityList',
+        success: res => {},fail: () => {},complete: () => {}
+      })
+    },
+    // 跳转组合支付
+    jumpCombination(item){
+      if (item.priceId) {
+        uni.navigateTo({
+          url: '/pages_category_page1/goodsModule/combination?priceId=' + item.priceId
+        })
+      } else {
+        uni.showToast({
+          title: '暂无活动',
+          icon: "none"
+        });
+      }
+    },
+    // 跳转到公告详情
+    jumpNoticeDetail(item){
+      uni.navigateTo({
+        url: '/pages_category_page2/userModule/messageDetail?noticeId=' + item.noticeId
+      })
+    },
+    // 跳转到直播列表
+    jumpLive(){
+      uni.navigateTo({
+        url: '/pages_category_page2/livePage/index'
+      })
+    },
+    // 领取优惠券
+    // receiveCoupon(item) {
+    //   var key = canvasConfig.getToken()
+    //   if (key) {
+    //     var paramsData = {}
+    //     if(this.typeId === 1){
+    //       paramsData.couponId = item.couponId
+    //     } else if(this.typeId === 3) {
+    //       paramsData.shopCouponId = item.shopCouponId
+    //       paramsData.shopId = this.shopId
+    //     }
+    //     let params = {
+    //       url: api.takeCoupon,
+    //       method: 'POST',
+    //       data: paramsData
+    //     }
+    //     this.sendReq(params, (res) => {
+    //       this.$message({
+    //         message: '领取成功!',
+    //         type: 'success'
+    //       })
+    //       this.getData()
+    //     })
+    //   } else {
+    //     this.$message({
+    //       message: '请先登录'
+    //     })
+    //     this.$router.push({path: '/login'})
+    //   }
+    // },
+    // 加入购物车
+    addCart(id){
+    }
+  }
+}

+ 9 - 0
components/canvasShow/config/mixin/index.js

@@ -0,0 +1,9 @@
+/*
+ * 用于组件复用
+ * 参考链接 https://cn.vuejs.org/v2/guide/mixins.html#全局混合
+ * 混合 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式。混合对象可以包含任意组件选项。以组件使用混合对象时,所有混合对象的选项将被混入该组件本身的选项。
+ */
+import { tool } from './funMixin.js'
+import { sendReq } from './sendReqMixin.js'
+export const funMixin = { ...tool }
+export const sendReqMixin = { ...sendReq }

+ 41 - 0
components/canvasShow/config/mixin/sendReqMixin.js

@@ -0,0 +1,41 @@
+/*
+ * 发送请求 mixin
+ */
+import request from './server'
+
+/* eslint-disable */
+export const sendReq = {
+  data() {
+    return {
+      // 加载中
+      loading: false,
+    }
+  },
+  methods: {
+    /*
+     * 发送请求
+     */
+    sendReq(params, callback) {
+      let self = this
+      request({
+        method: params.method || 'POST',
+        url: params.url,
+        data: params.data || {},
+        withCredentials: true,
+        headers: {
+          'Content-type':
+            params.contentType || 'application/json;charset=utf-8',
+        },
+      }).then(
+        (res) => {
+          if (res && res.data) {
+            callback && callback(res.data)
+          }
+        },
+        (error) => {
+          throw new Error(error)
+        }
+      )
+    },
+  },
+}

+ 132 - 0
components/canvasShow/config/mixin/server.js

@@ -0,0 +1,132 @@
+// 引入axios
+// import router from './../../router'
+import Vue from 'vue'
+// import promise from 'es6-promise'
+import axios from 'axios'
+import canvasConfig from '../config'
+// import Cookies from 'js-cookie'
+// import localStorage from '../storage/localStorage'
+// promise.polyfill()
+
+const service = axios.create({
+  headers: {
+    'X-Requested-With': 'XMLHttpRequest'
+  },
+  withCredentials: true,
+  timeout: 20000 // 请求超时 20s
+})
+
+// 请求拦截器
+service.interceptors.request.use(config => {
+  // 是否为当前的请求加上请求头 token
+  const token = canvasConfig.getToken()
+  if (token) {
+    if(canvasConfig.typeId === 1){
+      config.headers['Authorization-admin'] = token
+    } else if(canvasConfig.typeId === 3){
+      config.headers['Authorization-business'] = token
+    } else {
+      config.headers['Authorization'] = token
+    }
+  }
+  return config
+}, error => {
+  return Promise.reject(error)
+})
+
+// 响应拦截器
+service.interceptors.response.use(
+  (response) => {
+    if (response.data.code && response.data.code !=='200' && response.data.message) {
+      Vue.prototype.$message.error(response.data.message)
+    }
+    return response
+  },
+  err => {
+    // 失败响应
+    if (err && err.response) {
+      switch (err.response.status) {
+        case 400:
+          err.message = '请求无效,请检查参数是否正确!'
+          break
+
+        case 401:
+          err.message = '未经授权,访问被拒!'
+          break
+
+        case 403:
+          err.message = '拒绝访问!'
+          break
+
+        case 404:
+          err.message = `地址不存在!`
+          break
+
+        case 408:
+          err.message = '请求超时!'
+          break
+
+        case 500:
+          err.message = '系统错误!'
+          break
+
+        case 501:
+          err.message = '该方法未实现!'
+          break
+
+        case 502:
+          err.message = '网关出错!'
+          break
+
+        case 503:
+          err.message = '服务不可用!'
+          break
+
+        case 504:
+          err.message = '网关请求超时'
+          break
+
+        case 505:
+          err.message = 'HTTP版本不受支持'
+          break
+
+        default:
+      }
+      if (err.response.data.error) {
+        err.message = err.response.data.error
+      }
+      Vue.prototype.$message.closeAll()
+      Vue.prototype.$message.error(err.message)
+      // router.push({name: 'error', params: {message: err.message, status: err.response.status}})
+    }
+  }
+)
+
+//真机获取
+service.defaults.adapter = function (config) {
+  return new Promise((resolve, reject) => {
+    var settle = require('axios/lib/core/settle');
+    var buildURL = require('axios/lib/helpers/buildURL');
+    uni.request({
+      method: config.method.toUpperCase(),
+      url: buildURL(config.url, config.params, config.paramsSerializer),
+      header: config.headers,
+      data: config.data,
+      dataType: config.dataType,
+      responseType: config.responseType,
+      sslVerify: config.sslVerify,
+      complete:function complete(response){
+        response = {
+          data: response.data,
+          status: response.statusCode,
+          errMsg: response.errMsg,
+          header: response.header,
+          config: config
+        };
+
+        settle(resolve, reject, response);
+      }
+    })
+  })
+}
+export default service

TEMPAT SAMPAH
components/canvasShow/static/images/btn-next.png


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini