Browse Source

我的 优惠券和积分数据 积分列表 售后订单列表 售后订单详情 隐藏手机号绑定弹窗

落日晚风 1 year ago
parent
commit
39692952f0

+ 20 - 0
.env

@@ -0,0 +1,20 @@
+# 版本号
+SHOPRO_VERSION = v1.8.3
+
+# 正式环境接口域名
+SHOPRO_BASE_URL = https://api.shopro.sheepjs.com
+
+# 开发环境接口域名
+SHOPRO_DEV_BASE_URL = https://api.shopro.sheepjs.com
+
+# 开发环境运行端口
+SHOPRO_DEV_PORT = 3000
+
+# 接口地址前缀
+SHOPRO_API_PATH = /shop/api/
+
+# 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地  |  http(s)://xxx.xxx=自定义静态资源地址前缀
+SHOPRO_STATIC_URL = https://file.sheepjs.com
+
+# 是否开启直播  1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启)
+SHOPRO_MPLIVE_ON = 0

+ 3 - 3
pages/index/user.vue

@@ -3,13 +3,13 @@
     title="我的"
     tabbar="/pages/index/user"
     navbar="custom"
-    :bgStyle="template.style?.background"
+    :bgStyle="template.page"
     :navbarStyle="template.style?.navbar"
     onShareAppMessage
     :showFloatButton="true"
   >
-    <s-block v-for="(item, index) in template.data" :key="index" :styles="item.style">
-      <s-block-item :type="item.type" :data="item.data" :styles="item.style" />
+    <s-block v-for="(item, index) in template.components" :key="index" :styles="item.property.style">
+      <s-block-item :type="item.id" :data="item.property" :styles="item.property.style" />
     </s-block>
   </s-layout>
 </template>

+ 37 - 0
sheep/api/product/spu.js

@@ -0,0 +1,37 @@
+import request from '@/sheep/request';
+
+const SpuApi = {
+    // 获得商品 SPU 列表
+    getSpuList: (recommendType) => {
+        return request({
+            url: '/app-api/product/spu/list',
+            method: 'GET',
+            params: {recommendType},
+        });
+    },
+    // 获得商品 SPU 列表
+    getSpuListByIds: (ids) => {
+        return request({
+            url: '/app-api/product/spu/list-by-ids',
+            method: 'GET',
+            params: {ids},
+        });
+    },
+    // 获得商品 SPU 分页
+    getSpuPage: (data) => {
+        return request({
+            url: '/app-api/product/spu/page',
+            method: 'GET',
+            data
+        });
+    },
+    // 查询商品
+    getSpuDetail: (id) => {
+        return request({
+            url: '/app-api/product/spu/get-detail',
+            method: 'GET',
+            params: { id },
+        });
+    }
+};
+export default SpuApi;

+ 12 - 0
sheep/api/promotion/article.js

@@ -0,0 +1,12 @@
+import request from '@/sheep/request';
+
+export default {
+    // 获得文章详情
+    getArticle: (id) => {
+        return request({
+            url: '/app-api/promotion/article/get',
+            method: 'GET',
+            params: { id }
+        });
+    }
+}

+ 67 - 0
sheep/api/promotion/combination.js

@@ -0,0 +1,67 @@
+import request2 from "@/sheep/request2";
+
+// 拼团 API
+const CombinationApi = {
+    // 获得拼团活动列表
+    getCombinationActivityList: (count) => {
+        return request2({
+            url: "promotion/combination-activity/list",
+            method: 'GET',
+            params: {count}
+        });
+    },
+
+    // 获得拼团活动分页
+    getCombinationActivityPage: (params) => {
+        return request2({
+            url: "promotion/combination-activity/page",
+            method: 'GET',
+            params
+        });
+    },
+
+    // 获得拼团活动明细
+    getCombinationActivity: (id) => {
+        return request2({
+            url: "promotion/combination-activity/get-detail",
+            method: 'GET',
+            params: {
+                id
+            }
+        });
+    },
+
+    // 获得最近 n 条拼团记录(团长发起的)
+    getHeadCombinationRecordList: (activityId, status, count) => {
+        return request2({
+            url: "promotion/combination-record/get-head-list",
+            method: 'GET',
+            params: {
+                activityId,
+                status,
+                count
+            }
+        });
+    },
+
+    // 获得拼团记录明细
+    getCombinationRecordDetail: (id) => {
+        return request2({
+            url: "promotion/combination-record/get-detail",
+            method: 'GET',
+            params: {
+                id
+            }
+        });
+    },
+
+    // 获得拼团记录的概要信息
+    getCombinationRecordSummary: () => {
+        return request2({
+            url: "promotion/combination-record/get-summary",
+            method: 'GET',
+        });
+    }
+}
+
+export default CombinationApi

+ 12 - 0
sheep/api/promotion/coupon.js

@@ -0,0 +1,12 @@
+import request from '@/sheep/request';
+
+export default {
+    // 获得优惠劵模板列表
+    getCouponTemplateListByIds: (ids) => {
+        return request({
+            url: '/app-api/promotion/coupon-template/list-by-ids',
+            method: 'GET',
+            params: { ids }
+        });
+    }
+}

+ 33 - 0
sheep/api/promotion/seckill.js

@@ -0,0 +1,33 @@
+import request2 from "@/sheep/request2";
+
+const SeckillApi = {
+  // 获得秒杀时间段列表
+  getSeckillConfigList: () => {
+    return request2({ url: 'promotion/seckill-config/list', method: 'GET' });
+  },
+
+  // 获得当前秒杀活动
+  getNowSeckillActivity: () => {
+    return request2({ url: 'promotion/seckill-activity/get-now', method: 'GET' });
+  },
+
+  // 获得秒杀活动分页
+  getSeckillActivityPage: () => {
+    return request2({ url: 'promotion/seckill-activity/page', method: 'GET' });
+  },
+
+  /**
+   * 获得秒杀活动明细
+   * @param {number} id 秒杀活动编号
+   * @return {*}
+   */
+  getSeckillActivity: (id) => {
+    return request2({
+      url: 'promotion/seckill-activity/get-detail',
+      method: 'GET',
+      params: { id }
+    });
+  }
+}
+
+export default SeckillApi;

+ 22 - 22
sheep/components/s-block-item/s-block-item.vue

@@ -18,40 +18,40 @@
     <!-- 基础组件:标题栏 -->
     <s-title-block v-if="type === 'titleBlock'" :data="data" :styles="styles" />
     <!-- 图文组件:广告魔方 -->
-    <s-image-cube v-if="type === 'imageCube'" :data="data" :styles="styles" />
+    <s-image-cube v-if="type === 'MagicCube'" :data="data" :styles="styles" />
     <!-- 图文组件:视频播放 -->
     <s-video-block v-if="type === 'VideoPlayer'" :data="data" :styles="styles" />
-    <!-- 基础组件:辅助线 -->
-    <s-line-block v-if="type === 'lineBlock'" :data="data" />
-    <!-- 图文组件:富文字 -->
-    <s-richtext-block v-if="type === 'richtext'" :data="data" :styles="styles" />
+    <!-- 基础组件:分割线 -->
+    <s-line-block v-if="type === 'Divider'" :data="data" />
     <!-- 图文组件:热区 -->
     <s-hotzone-block v-if="type === 'hotzone'" :data="data" :styles="styles" />
 
     <!-- 商品组件:商品卡片 -->
-    <s-goods-card v-if="type === 'goodsCard'" :data="data" :styles="styles" />
+    <s-goods-card v-if="type === 'ProductCard'" :data="data" :styles="styles" />
     <!-- 商品组件:商品栏 -->
-    <s-goods-shelves v-if="type === 'goodsShelves'" :data="data" :styles="styles" />
+    <s-goods-shelves v-if="type === 'ProductList'" :data="data" :styles="styles" />
 
     <!-- 营销组件:拼团 -->
-    <s-groupon-block v-if="type === 'groupon'" :data="data" :styles="styles" />
+    <s-groupon-block v-if="type === 'PromotionCombination'" :data="data" :styles="styles" />
     <!-- 营销组件:秒杀 -->
-    <s-seckill-block v-if="type === 'seckill'" :data="data" :styles="styles" />
-    <!-- 营销组件:积分商城 -->
-    <s-score-block v-if="type === 'scoreGoods'" :data="data" :styles="styles" />
-    <!-- 营销组件:小程序直播 -->
-    <s-live-block v-if="type === 'mplive'" :data="data" :styles="styles" />
+    <s-seckill-block v-if="type === 'PromotionSeckill'" :data="data" :styles="styles" />
+    <!-- 营销组件:积分商城(模式不一样,无法适配) -->
+    <s-score-block v-if="type === 'PromotionPoint'" :data="data" :styles="styles" />
+    <!-- 营销组件:小程序直播(暂时没有这个功能) -->
+    <s-live-block v-if="type === 'MpLive'" :data="data" :styles="styles" />
     <!-- 营销组件:优惠券 -->
-    <s-coupon-block v-if="type === 'coupon'" :data="data" :styles="styles" />
+    <s-coupon-block v-if="type === 'CouponCard'" :data="data" :styles="styles" />
+    <!-- 营销组件:文章 -->
+    <s-richtext-block v-if="type === 'PromotionArticle'" :data="data" :styles="styles" />
 
-    <!-- 会员组件:会员卡片 -->
-    <s-user-card v-if="type === 'userCard'" />
-    <!-- 会员组件:订单卡片 -->
-    <s-order-card v-if="type === 'orderCard'" :data="data" />
-    <!-- 会员组件:资产卡片 -->
-    <s-wallet-card v-if="type === 'walletCard'" />
-    <!-- 会员组件:卡券卡片 -->
-    <s-coupon-card v-if="type === 'couponCard'" />
+    <!-- 用户组件:用户卡片 -->
+    <s-user-card v-if="type === 'UserCard'" />
+    <!-- 用户组件:用户订单 -->
+    <s-order-card v-if="type === 'UserOrder'" :data="data" />
+    <!-- 用户组件:用户资产 -->
+    <s-wallet-card v-if="type === 'UserWallet'" />
+    <!-- 用户组件:用户卡券 -->
+    <s-coupon-card v-if="type === 'UserCoupon'" />
   </view>
 </template>
 

+ 92 - 133
sheep/components/s-coupon-block/s-coupon-block.vue

@@ -1,126 +1,54 @@
 <!-- 优惠券组  -->
 <template>
-  <view>
-    <!-- 样式1 -->
-    <view class="lg-coupon-wrap" v-if="mode == 1">
-      <scroll-view class="scroll-box" scroll-x scroll-anchoring>
-        <view class="coupon-box ss-flex">
-          <view
-            class="coupon-item"
-            :style="[couponBg, { marginLeft: data.space + 'px' }]"
-            v-for="(item, index) in couponList"
-            :key="index"
-          >
-            <su-coupon
-              size="lg"
-              :textColor="data.fill.color"
-              background=""
-              :couponId="item.id"
-              :title="item.amount_text"
-              :value="item.amount"
-              :surplus="item.stock"
-              :type="item.type"
-              :sellBy="`${item.get_start_time.substring(0, 10)} 至 ${item.get_end_time.substring(
-                0,
-                10,
-              )}`"
+  <scroll-view class="scroll-box" scroll-x scroll-anchoring>
+    <view class="coupon-box ss-flex">
+      <view
+        class="coupon-item"
+        :style="[couponBg, { marginLeft: `${data.space}px` }]"
+        v-for="(item, index) in couponList"
+        :key="index"
+      >
+        <su-coupon
+          :size="SIZE_LIST[columns - 1]"
+          :textColor="data.textColor"
+          background=""
+          :couponId="item.id"
+          :title="item.name"
+          :type="formatCouponDiscountType(item)"
+          :value="formatCouponDiscountValue(item)"
+          :sellBy="formatValidityType(item)"
+        >
+          <template v-slot:btn>
+            <!-- 两列时,领取按钮坚排 -->
+            <button
+              v-if="columns === 2"
+              @click.stop="onGetCoupon(item.id)"
+              class="ss-reset-button card-btn vertical"
+              :style="[btnStyles]"
             >
-              <template v-slot:btn>
-                <button
-                  class="ss-reset-button card-btn"
-                  :style="[btnStyles]"
-                  @click.stop="onGetCoupon(item.id)"
-                >
-                  {{ item.get_status_text }}
-                </button>
-              </template>
-            </su-coupon>
-          </view>
-        </view>
-      </scroll-view>
-    </view>
-    <!-- 样式2 -->
-    <view class="md-coupon-wrap" v-if="mode == 2">
-      <scroll-view class="scroll-box" scroll-x scroll-anchoring>
-        <view class="coupon-box ss-flex">
-          <view
-            class="coupon-item"
-            :style="[couponBg, { marginLeft: data.space + 'px' }]"
-            v-for="(item, index) in couponList"
-            :key="index"
-          >
-            <su-coupon
-              size="md"
-              :textColor="data.fill.color"
-              background=""
-              :title="item.amount_text"
-              :value="item.amount"
-              :surplus="item.stock"
-              :couponId="item.id"
-              :type="item.type"
-              :sellBy="`${item.get_start_time.substring(0, 10)} 至 ${item.get_end_time.substring(
-                0,
-                10,
-              )}`"
-            >
-              <template v-slot:btn>
-                <button
-                  @click.stop="onGetCoupon(item.id)"
-                  class="ss-reset-button card-btn ss-flex ss-row-center ss-col-center"
-                  :style="[btnStyles]"
-                >
-                  <view class="btn-text">{{ item.get_status_text }}</view>
-                </button>
-              </template>
-            </su-coupon>
-          </view>
-        </view>
-      </scroll-view>
-    </view>
-    <!-- 样式3 -->
-    <view class="xs-coupon-wrap" v-if="mode == 3">
-      <scroll-view class="scroll-box" scroll-x scroll-anchoring>
-        <view class="coupon-box ss-flex">
-          <view
-            class="coupon-item"
-            :style="[couponBg, { marginLeft: data.space + 'px' }]"
-            v-for="(item, index) in couponList"
-            :key="index"
-          >
-            <su-coupon
-              size="xs"
-              :textColor="data.fill.color"
-              background=""
-              :title="item.amount_text"
-              :value="item.amount"
-              :surplus="item.stock"
-              :couponId="item.id"
-              :type="item.type"
-              :sellBy="`${item.get_start_time.substring(0, 10)} 至 ${item.get_end_time.substring(
-                0,
-                10,
-              )}`"
+              <view class="btn-text">立即领取</view>
+            </button>
+            <button
+              v-else
+              class="ss-reset-button card-btn"
+              :style="[btnStyles]"
+              @click.stop="onGetCoupon(item.id)"
             >
-              <template v-slot:btn>
-                <button
-                  class="ss-reset-button card-btn"
-                  :style="[btnStyles]"
-                  @click.stop="onGetCoupon(item.id)"
-                >
-                  {{ item.get_status_text }}
-                </button>
-              </template>
-            </su-coupon>
-          </view>
-        </view>
-      </scroll-view>
+              立即领取
+            </button>
+          </template>
+        </su-coupon>
+      </view>
     </view>
-  </view>
+  </scroll-view>
 </template>
 
 <script setup>
   import sheep from '@/sheep';
+  import TemplateApi from '@/sheep/api/promotion/coupon';
   import { ref, onMounted } from 'vue';
+  import {CouponTemplateValidityTypeEnum, PromotionDiscountTypeEnum} from "@/sheep/util/const";
+  import {floatToFixed2, formatDate} from "@/sheep/util";
 
   const props = defineProps({
     data: {
@@ -132,14 +60,48 @@
       default: () => ({}),
     },
   });
-  const { mode, button } = props.data;
+  const { columns, button } = props.data;
+  const SIZE_LIST = ['lg', 'md', 'xs']
   const couponBg = {
-    background: `url(${sheep.$url.cdn(props.data.fill.bgImage)}) no-repeat top center / 100% 100%`,
+    background: `url(${sheep.$url.cdn(props.data.bgImg)}) no-repeat top center / 100% 100%`,
   };
   const btnStyles = {
     background: button.bgColor,
     color: button.color,
   };
+
+  // 格式化【折扣类型】
+  const formatCouponDiscountType = (coupon) => {
+    if(coupon.discountType === PromotionDiscountTypeEnum.PRICE.type) {
+      return 'reduce'
+    }
+    if(coupon.discountType === PromotionDiscountTypeEnum.PERCENT.type) {
+      return 'percent'
+    }
+    return `未知【${coupon.discountType}】`
+  }
+
+  // 格式化【折扣】
+  const formatCouponDiscountValue = (coupon) => {
+    if(coupon.discountType === PromotionDiscountTypeEnum.PRICE.type) {
+      return floatToFixed2(coupon.discountPrice)
+    }
+    if(coupon.discountType === PromotionDiscountTypeEnum.PERCENT.type) {
+      return coupon.discountPercent
+    }
+    return `未知【${coupon.discountType}】`
+  }
+  // 格式化【有效期限】
+  const formatValidityType = (row) => {
+    if (row.validityType === CouponTemplateValidityTypeEnum.DATE.type) {
+      return `${formatDate(row.validStartTime)} 至 ${formatDate(row.validEndTime)}`
+    }
+    if (row.validityType === CouponTemplateValidityTypeEnum.TERM.type) {
+      return `领取后第 ${row.fixedStartTerm} - ${row.fixedEndTerm} 天内可用`
+    }
+    return '未知【' + row.validityType + '】'
+  }
+
   const couponList = ref([]);
   //立即领取优惠券
   async function onGetCoupon(id) {
@@ -149,15 +111,16 @@
         title: msg,
         icon: 'none',
       });
-    } else {
-      let { data } = await sheep.$api.coupon.list({ ids: props.data.couponIds.join(',') });
-      couponList.value = [...data.data];
+      return
     }
+    await getCouponTemplateList()
+  }
+  const getCouponTemplateList = async () => {
+    const { data } = await TemplateApi.getCouponTemplateListByIds(props.data.couponIds.join(','));
+    couponList.value = data;
   }
-  onMounted(async () => {
-    let { data } = await sheep.$api.coupon.list({ ids: props.data.couponIds.join(',') });
-    // couponList.value = [...data.data, ...data.data, ...data.data, ...data.data];
-    couponList.value = [...data.data];
+  onMounted(() => {
+    getCouponTemplateList()
   });
 </script>
 
@@ -168,25 +131,21 @@
     border-radius: 25rpx;
     font-size: 24rpx;
     line-height: 50rpx;
-  }
-  .coupon-item {
-    &:nth-of-type(1) {
-      margin-left: 0 !important;
-    }
-  }
-  .md-coupon-wrap {
-    .card-btn {
+    &.vertical {
       width: 50rpx;
       height: 140rpx;
-      margin: auto 0;
-      margin-right: 20rpx;
+      margin: auto 20rpx auto 0;
 
       .btn-text {
         font-size: 24rpx;
         text-align: center;
         writing-mode: vertical-lr;
-        writing-mode: tb-lr;
       }
     }
   }
+  .coupon-item {
+    &:nth-of-type(1) {
+      margin-left: 0 !important;
+    }
+  }
 </style>

+ 93 - 58
sheep/components/s-goods-card/s-goods-card.vue

@@ -1,8 +1,8 @@
 <template>
   <!-- 商品卡片 -->
   <view>
-    <!-- 1 100%宽卡片列表-->
-    <view v-if="mode === 1 && state.goodsList.length" class="goods-sl-box">
+    <!-- 布局1. 单列大图(上图,下内容)-->
+    <view v-if="layoutType === LayoutTypeEnum.ONE_COL_BIG_IMG && state.goodsList.length" class="goods-sl-box">
       <view
         class="goods-box"
         v-for="item in state.goodsList"
@@ -12,27 +12,28 @@
         <s-goods-column
           class=""
           size="sl"
-          :goodsFields="goodsFields"
-          :tagStyle="tagStyle"
+          :goodsFields="data.fields"
+          :tagStyle="data.badge"
           :data="item"
-          :titleColor="goodsFields.title?.color"
-          :subTitleColor="goodsFields.subtitle.color"
+          :titleColor="data.fields.name?.color"
+          :subTitleColor="data.fields.introduction.color"
           :topRadius="data.borderRadiusTop"
           :bottomRadius="data.borderRadiusBottom"
           @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
         >
+          <!-- 购买按钮 -->
           <template v-slot:cart>
             <button class="ss-reset-button cart-btn" :style="[buyStyle]">
-              {{ buyNowStyle.mode === 1 ? buyNowStyle.text : '' }}
+              {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
             </button>
           </template>
         </s-goods-column>
       </view>
     </view>
 
-    <!-- 2   双列瀑布流列表-->
+    <!-- 布局2. 双列(每一列:上图,下内容)-->
     <view
-      v-if="mode === 2 && state.goodsList.length"
+      v-if="layoutType === LayoutTypeEnum.TWO_COL && state.goodsList.length"
       class="goods-md-wrap ss-flex ss-flex-wrap ss-col-top"
     >
       <view class="goods-list-box">
@@ -45,20 +46,21 @@
           <s-goods-column
             class="goods-md-box"
             size="md"
-            :goodsFields="goodsFields"
-            :tagStyle="tagStyle"
+            :goodsFields="data.fields"
+            :tagStyle="data.badge"
             :data="item"
-            :titleColor="goodsFields.title?.color"
-            :subTitleColor="goodsFields.subtitle.color"
+            :titleColor="data.fields.name?.color"
+            :subTitleColor="data.fields.introduction.color"
             :topRadius="data.borderRadiusTop"
             :bottomRadius="data.borderRadiusBottom"
             :titleWidth="330 - marginLeft - marginRight"
             @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
-            @getHeight="mountMasonry($event, 'left')"
+            @getHeight="calculateGoodsColumn($event, 'left')"
           >
+            <!-- 购买按钮 -->
             <template v-slot:cart>
               <button class="ss-reset-button cart-btn" :style="[buyStyle]">
-                {{ buyNowStyle.mode === 1 ? buyNowStyle.text : '' }}
+                {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
               </button>
             </template>
           </s-goods-column>
@@ -74,20 +76,21 @@
           <s-goods-column
             class="goods-md-box"
             size="md"
-            :goodsFields="goodsFields"
-            :tagStyle="tagStyle"
+            :goodsFields="data.fields"
+            :tagStyle="data.badge"
             :data="item"
-            :titleColor="goodsFields.title?.color"
-            :subTitleColor="goodsFields.subtitle.color"
+            :titleColor="data.fields.name?.color"
+            :subTitleColor="data.fields.introduction.color"
             :topRadius="data.borderRadiusTop"
             :bottomRadius="data.borderRadiusBottom"
             :titleWidth="330 - marginLeft - marginRight"
             @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
-            @getHeight="mountMasonry($event, 'right')"
+            @getHeight="calculateGoodsColumn($event, 'right')"
           >
+            <!-- 购买按钮 -->
             <template v-slot:cart>
               <button class="ss-reset-button cart-btn" :style="[buyStyle]">
-                {{ buyNowStyle.mode === 1 ? buyNowStyle.text : '' }}
+                {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
               </button>
             </template>
           </s-goods-column>
@@ -95,8 +98,8 @@
       </view>
     </view>
 
-    <!-- 3  30%卡片列表-->
-    <view v-if="mode === 3 && state.goodsList.length" class="goods-lg-box">
+    <!-- 布局3. 单列小图(左图,右内容) -->
+    <view v-if="layoutType === LayoutTypeEnum.ONE_COL_SMALL_IMG && state.goodsList.length" class="goods-lg-box">
       <view
         class="goods-box"
         :style="[{ marginBottom: data.space + 'px' }]"
@@ -106,18 +109,19 @@
         <s-goods-column
           class="goods-card"
           size="lg"
-          :goodsFields="goodsFields"
+          :goodsFields="data.fields"
           :data="item"
-          :tagStyle="tagStyle"
-          :titleColor="goodsFields.title?.color"
-          :subTitleColor="goodsFields.subtitle.color"
+          :tagStyle="data.badge"
+          :titleColor="data.fields.name?.color"
+          :subTitleColor="data.fields.introduction.color"
           :topRadius="data.borderRadiusTop"
           :bottomRadius="data.borderRadiusBottom"
           @tap="sheep.$router.go('/pages/goods/index', { id: item.id })"
         >
+          <!-- 购买按钮 -->
           <template v-slot:cart>
             <button class="ss-reset-button cart-btn" :style="[buyStyle]">
-              {{ buyNowStyle.mode === 1 ? buyNowStyle.text : '' }}
+              {{ btnBuy.type === 'text' ? btnBuy.text : '' }}
             </button>
           </template>
         </s-goods-column>
@@ -128,11 +132,21 @@
 
 <script setup>
   /**
-   * 商品模板,装修商品卡片
-   * @description style 1:带tab 2:瀑布流,横向两个,上图下内容 3:大图,横向一个
+   * 商品卡片
    */
   import { computed, reactive, onMounted } from 'vue';
   import sheep from '@/sheep';
+  import SpuApi from '@/sheep/api/product/spu';
+
+  // 布局类型
+  const LayoutTypeEnum = {
+    // 单列大图
+    ONE_COL_BIG_IMG: 'oneColBigImg',
+    // 双列
+    TWO_COL: 'twoCol',
+    // 单列小图
+    ONE_COL_SMALL_IMG: 'oneColSmallImg',
+  }
 
   const state = reactive({
     goodsList: [],
@@ -150,55 +164,76 @@
     },
   });
 
-  const { mode, tagStyle, buyNowStyle, goodsFields, goodsIds } = props.data ?? {};
+  const { layoutType, btnBuy, spuIds } = props.data ?? {};
   const { marginLeft, marginRight } = props.styles ?? {};
 
-  async function getGoodsListByIds(ids) {
-    let { data } = await sheep.$api.goods.ids({ ids });
-    return data;
-  }
-
-  onMounted(async () => {
-    state.goodsList = await getGoodsListByIds(goodsIds.join(','));
-    if (mode === 2) {
-      mountMasonry();
+  // 购买按钮样式
+  const buyStyle = computed(() => {
+    if (btnBuy.type === 'text') {
+      // 文字按钮:线性渐变背景颜色
+      return {
+        background: `linear-gradient(to right, ${btnBuy.bgBeginColor}, ${btnBuy.bgEndColor})`,
+      };
+    }
+    if (btnBuy.type === 'img') {
+      // 图片按钮
+      return {
+        width: '54rpx',
+        height: '54rpx',
+        background: `url(${sheep.$url.cdn(btnBuy.imgUrl)}) no-repeat`,
+        backgroundSize: '100% 100%',
+      };
     }
   });
 
-  // 加载瀑布流
+  //region 商品瀑布流布局
+  // 下一个要处理的商品索引
   let count = 0;
+  // 左列的高度
   let leftHeight = 0;
+  // 右列的高度
   let rightHeight = 0;
 
-  function mountMasonry(height = 0, where = 'left') {
+  /**
+   * 计算商品在左列还是右列
+   * @param height 商品的高度
+   * @param where 添加到哪一列
+   */
+  function calculateGoodsColumn(height = 0, where = 'left') {
+    // 处理完
     if (!state.goodsList[count]) return;
+    // 增加列的高度
     if (where === 'left') leftHeight += height;
     if (where === 'right') rightHeight += height;
+    // 添加到矮的一列
     if (leftHeight <= rightHeight) {
       state.leftGoodsList.push(state.goodsList[count]);
     } else {
       state.rightGoodsList.push(state.goodsList[count]);
     }
+    // 计数
     count++;
   }
+  //endregion
 
-  // 购买按钮样式
-  const buyStyle = computed(() => {
-    if (buyNowStyle.mode == 1) {
-      // button
-      return {
-        background: `linear-gradient(to right, ${buyNowStyle.color1}, ${buyNowStyle.color2})`,
-      };
-    }
+  /**
+   * 根据商品编号列表,获取商品列表
+   * @param ids 商品编号列表
+   * @return {Promise<undefined>} 商品列表
+   */
+  async function getGoodsListByIds(ids) {
+    const { data } = await SpuApi.getSpuListByIds(ids);
+    return data;
+  }
 
-    if (buyNowStyle.mode == 2) {
-      // image
-      return {
-        width: '54rpx',
-        height: '54rpx',
-        background: `url(${sheep.$url.cdn(buyNowStyle.src)}) no-repeat`,
-        backgroundSize: '100% 100%',
-      };
+  // 初始化
+  onMounted(async () => {
+    // 加载商品列表
+    state.goodsList = await getGoodsListByIds(spuIds.join(','));
+    // 只有双列布局时需要
+    if (layoutType === LayoutTypeEnum.TWO_COL){
+      // 分列
+      calculateGoodsColumn();
     }
   });
 </script>

+ 41 - 28
sheep/components/s-goods-column/s-goods-column.vue

@@ -9,19 +9,19 @@
       @tap="onClick"
     >
       <view v-if="tagStyle.show" class="tag-icon-box">
-        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src)"></image>
+        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
       </view>
-      <image class="xs-img-box" :src="sheep.$url.cdn(data.image)" mode="aspectFit"></image>
+      <image class="xs-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFit"></image>
       <view
-        v-if="goodsFields.title?.show || goodsFields.price?.show"
+        v-if="goodsFields.title?.show || goodsFields.name?.show || goodsFields.price?.show"
         class="xs-goods-content ss-flex-col ss-row-around"
       >
         <view
-          v-if="goodsFields.title?.show"
+          v-if="goodsFields.title?.show || goodsFields.name?.show"
           class="xs-goods-title ss-line-1"
           :style="[{ color: titleColor, width: titleWidth ? titleWidth + 'rpx' : '' }]"
         >
-          {{ data.title }}
+          {{ data.title || data.name }}
         </view>
         <view
           v-if="goodsFields.price?.show"
@@ -37,17 +37,17 @@
     <!-- sm卡片:竖向紧凑,一行放三个,图上内容下 -->
     <view v-if="size === 'sm'" class="sm-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick">
       <view v-if="tagStyle.show" class="tag-icon-box">
-        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src)"></image>
+        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
       </view>
-      <image class="sm-img-box" :src="sheep.$url.cdn(data.image)" mode="aspectFill"></image>
+      <image class="sm-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image>
 
       <view
-        v-if="goodsFields.title?.show || goodsFields.price?.show"
+        v-if="goodsFields.title?.show || goodsFields.name?.show || goodsFields.price?.show"
         class="sm-goods-content"
         :style="[{ color: titleColor, width: titleWidth ? titleWidth + 'rpx' : '' }]"
       >
-        <view v-if="goodsFields.title?.show" class="sm-goods-title ss-line-1 ss-m-b-16">
-          {{ data.title }}
+        <view v-if="goodsFields.title?.show || goodsFields.name?.show" class="sm-goods-title ss-line-1 ss-m-b-16">
+          {{ data.title || data.name }}
         </view>
         <view
           v-if="goodsFields.price?.show"
@@ -63,26 +63,26 @@
     <!-- md卡片:竖向,一行放两个,图上内容下 -->
     <view v-if="size === 'md'" class="md-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick">
       <view v-if="tagStyle.show" class="tag-icon-box">
-        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src)"></image>
+        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
       </view>
-      <image class="md-img-box" :src="sheep.$url.cdn(data.image||data.picUrl)" mode="widthFix"></image>
+      <image class="md-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="widthFix"></image>
       <view
         class="md-goods-content ss-flex-col ss-row-around ss-p-b-20 ss-p-t-20 ss-p-x-16"
         :id="elId"
       >
         <view
-          v-if="goodsFields.title?.show"
+          v-if="goodsFields.title?.show || goodsFields.name?.show"
           class="md-goods-title ss-line-1"
           :style="[{ color: titleColor, width: titleWidth ? titleWidth + 'rpx' : '' }]"
         >
-          {{ data.title||data.name }}
+          {{ data.title || data.name }}
         </view>
         <view
-          v-if="goodsFields.subtitle?.show"
+          v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
           class="md-goods-subtitle ss-m-t-16 ss-line-1"
           :style="[{ color: subTitleColor, background: subTitleBackground }]"
         >
-          {{ data.subtitle }}
+          {{ data.subtitle || data.introduction }}
         </view>
         <slot name="activity">
           <view v-if="data.promos?.length" class="tag-box ss-flex-wrap ss-flex ss-col-center">
@@ -135,28 +135,28 @@
       @tap="onClick"
     >
       <view v-if="tagStyle.show" class="tag-icon-box">
-        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src)"></image>
+        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
       </view>
       <view v-if="seckillTag" class="seckill-tag ss-flex ss-row-center"> 秒杀 </view>
       <view v-if="grouponTag" class="groupon-tag ss-flex ss-row-center">
         <view class="tag-icon">拼团</view>
       </view>
-      <image class="lg-img-box" :src="sheep.$url.cdn(data.image||data.picUrl)" mode="aspectFill"></image>
+      <image class="lg-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image>
       <view class="lg-goods-content ss-flex-1 ss-flex-col ss-row-between ss-p-b-10 ss-p-t-20">
         <view>
           <view
-            v-if="goodsFields.title?.show"
+            v-if="goodsFields.title?.show || goodsFields.name?.show"
             class="lg-goods-title ss-line-2"
             :style="[{ color: titleColor }]"
           >
-            {{ data.title||data.name }}
+            {{ data.title || data.name }}
           </view>
           <view
-            v-if="goodsFields.subtitle?.show"
+            v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
             class="lg-goods-subtitle ss-m-t-10 ss-line-1"
             :style="[{ color: subTitleColor, background: subTitleBackground }]"
           >
-            {{ data.subtitle }}
+            {{ data.subtitle || data.introduction }}
           </view>
         </view>
         <view>
@@ -201,26 +201,26 @@
     <!-- sl卡片:竖向型,一行放一个,图片上内容下边 -->
     <view v-if="size === 'sl'" class="sl-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick">
       <view v-if="tagStyle.show" class="tag-icon-box">
-        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src)"></image>
+        <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
       </view>
 
-      <image class="sl-img-box" :src="sheep.$url.cdn(data.image||data.picUrl)" mode="aspectFill"></image>
+      <image class="sl-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image>
 
       <view class="sl-goods-content">
         <view>
           <view
-            v-if="goodsFields.title?.show"
+            v-if="goodsFields.title?.show || goodsFields.name?.show"
             class="sl-goods-title ss-line-1"
             :style="[{ color: titleColor }]"
           >
-            {{ data.title||data.name }}
+            {{ data.title || data.name }}
           </view>
           <view
-            v-if="goodsFields.subtitle?.show"
+            v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
             class="sl-goods-subtitle ss-m-t-16"
             :style="[{ color: subTitleColor, background: subTitleBackground }]"
           >
-            {{ data.subtitle }}
+            {{ data.subtitle || data.introduction }}
           </view>
         </view>
         <view>
@@ -307,12 +307,25 @@
       type: [Array, Object],
       default() {
         return {
+          // 商品名称(旧)
           title: { show: true },
+          // 商品介绍(旧)
           subtitle: { show: true },
+          // 商品价格
           price: { show: true },
+          // 市场价(旧)
           original_price: { show: true },
+          // 销量(旧)
           sales: { show: true },
+          // 库存
           stock: { show: true },
+          // 商品名称(新)
+          name: { show: true },
+          // 商品介绍(新)
+          introduction: { show: true },
+          // 市场价(新)
+          marketPrice: { show: true },
+          // 销量(新)
           salesCount: { show: true },
         };
       },

+ 21 - 22
sheep/components/s-goods-shelves/s-goods-shelves.vue

@@ -1,8 +1,8 @@
 <template>
   <view>
-    <!-- 1  两张图片并排 图片坐文案右 -->
+    <!-- 布局1. 两列商品,图片左文案右 -->
     <view
-      v-if="mode === 1"
+      v-if="layoutType === 'twoCol'"
       class="goods-xs-box ss-flex ss-flex-wrap"
       :style="[{ margin: '-' + data.space + 'rpx' }]"
     >
@@ -19,10 +19,10 @@
         <s-goods-column
           class="goods-card"
           size="xs"
-          :goodsFields="goodsFields"
-          :tagStyle="tagStyle"
+          :goodsFields="data.fields"
+          :tagStyle="data.badge"
           :data="item"
-          :titleColor="goodsFields.title?.color"
+          :titleColor="data.fields.name?.color"
           :topRadius="data.borderRadiusTop"
           :bottomRadius="data.borderRadiusBottom"
           :titleWidth="(454 - marginRight * 2 - data.space * 2 - marginLeft * 2) / 2"
@@ -30,9 +30,9 @@
         ></s-goods-column>
       </view>
     </view>
-    <!-- 2  三张商品卡片并排 图片上文案下 -->
+    <!-- 布局. 三列商品:图片上文案下 -->
     <view
-      v-if="mode === 2"
+      v-if="layoutType === 'threeCol'"
       class="goods-sm-box ss-flex ss-flex-wrap"
       :style="[{ margin: '-' + data.space + 'rpx' }]"
     >
@@ -49,10 +49,10 @@
         <s-goods-column
           class="goods-card"
           size="sm"
-          :goodsFields="goodsFields"
-          :tagStyle="tagStyle"
+          :goodsFields="data.fields"
+          :tagStyle="data.badge"
           :data="item"
-          :titleColor="goodsFields.title?.color"
+          :titleColor="data.fields.name?.color"
           :topRadius="data.borderRadiusTop"
           :bottomRadius="data.borderRadiusBottom"
           @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
@@ -60,8 +60,8 @@
       </view>
     </view>
 
-    <!-- 3 商品卡片并排 轮播 -->
-    <view v-if="mode === 3" class="">
+    <!-- 布局3. 一行商品,水平滑动 -->
+    <view v-if="layoutType === 'horizSwiper'" class="">
       <scroll-view class="scroll-box goods-scroll-box" scroll-x scroll-anchoring>
         <view class="goods-box ss-flex">
           <view
@@ -73,10 +73,10 @@
             <s-goods-column
               class="goods-card"
               size="sm"
-              :goodsFields="goodsFields"
-              :tagStyle="tagStyle"
+              :goodsFields="data.fields"
+              :tagStyle="data.badge"
               :data="item"
-              :titleColor="goodsFields.title?.color"
+              :titleColor="data.fields.name?.color"
               :titleWidth="(750 - marginRight * 2 - data.space * 4 - marginLeft * 2) / 3"
               @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
             ></s-goods-column>
@@ -89,12 +89,11 @@
 
 <script setup>
   /**
-   * 商品栏s-goods-column
-   *
-   * @description style 1:横向两个,左图右内容 2:横向三个,上图下内容 3:左右滚动
+   * 商品栏
    */
-  import { onMounted, ref } from 'vue';
+  import { onMounted, ref, computed } from 'vue';
   import sheep from '@/sheep';
+  import SpuApi from "@/sheep/api/product/spu";
 
   const props = defineProps({
     data: {
@@ -106,12 +105,12 @@
       default() {},
     },
   });
-  const { mode, tagStyle, buyNowStyle, goodsFields, goodsIds } = props.data;
+  const { layoutType, spuIds } = props.data;
   let { marginLeft, marginRight } = props.styles;
   const goodsList = ref([]);
   onMounted(async () => {
-    if (goodsIds.length > 0) {
-      let { data } = await sheep.$api.goods.ids({ ids: goodsIds.join(',') });
+    if (spuIds.length > 0) {
+      let { data } = await SpuApi.getSpuListByIds(spuIds.join(','));
       goodsList.value = data;
     }
   });

+ 28 - 28
sheep/components/s-groupon-block/s-groupon-block.vue

@@ -2,13 +2,13 @@
 <template>
   <view>
     <view
-      v-if="mode === 1"
+      v-if="layoutType === 'threeCol'"
       class="goods-sm-box ss-flex ss-flex-wrap"
       :style="[{ margin: '-' + data.space + 'rpx' }]"
     >
       <view
-        v-for="item in goodsList"
-        :key="item.id"
+        v-for="product in productList"
+        :key="product.id"
         class="goods-card-box"
         :style="[
           {
@@ -19,15 +19,15 @@
         <s-goods-column
           class="goods-card"
           size="sm"
-          :goodsFields="goodsFields"
+          :goodsFields="data.fields"
           :tagStyle="tagStyle"
-          :data="item"
-          :titleColor="goodsFields.title?.color"
+          :data="product"
+          :titleColor="data.fields.name?.color"
           :topRadius="data.borderRadiusTop"
           :bottomRadius="data.borderRadiusBottom"
           @click="
             sheep.$router.go('/pages/goods/groupon', {
-              id: item.id,
+              id: product.id,
               activity_id: props.data.activityId,
             })
           "
@@ -35,33 +35,33 @@
       </view>
     </view>
     <!-- 样式2 一行一个 图片左 文案右 -->
-    <view class="goods-box" v-if="mode == 2">
+    <view class="goods-box" v-if="layoutType === 'oneCol'">
       <view
         class="goods-list"
-        v-for="(item, index) in goodsList"
+        v-for="(product, index) in productList"
         :key="index"
         :style="[{ marginBottom: space + 'px' }]"
       >
         <s-goods-column
           class="goods-card"
           size="lg"
-          :includes="goodsFields"
+          :goodsFields="data.fields"
           :tagStyle="tagStyle"
-          :data="item"
-          :titleColor="goodsFields.title?.color"
-          :subTitleColor="goodsFields.subtitle.color"
+          :data="product"
+          :titleColor="data.fields.name?.color"
+          :subTitleColor="data.fields.introduction?.color"
           :topRadius="data.borderRadiusTop"
           :bottomRadius="data.borderRadiusBottom"
           @click="
             sheep.$router.go('/pages/goods/groupon', {
-              id: item.id,
+              id: product.id,
               activity_id: props.data.activityId,
             })
           "
         >
           <template v-slot:cart>
             <button class="ss-reset-button cart-btn" :style="[buyStyle]">
-              {{ buyNowStyle.mode === 1 ? buyNowStyle.text : '' }}
+              {{ btnBuy?.type === 'text' ? btnBuy.text : '' }}
             </button>
           </template>
         </s-goods-column>
@@ -73,13 +73,11 @@
 <script setup>
   /**
    * 拼团
-   *
-   * @property {Array} list 											- 商品列表
-   *
-   *
    */
   import { computed, onMounted, ref } from 'vue';
   import sheep from '@/sheep';
+  import SpuApi from "@/sheep/api/product/spu";
+  import CombinationApi from "@/sheep/api/promotion/combination";
 
   // 接收参数
   const props = defineProps({
@@ -93,32 +91,34 @@
     },
   });
 
-  let { mode, tagStyle, buyNowStyle, goodsFields, space } = props.data;
+  let { layoutType, tagStyle, btnBuy, space } = props.data;
   let { marginLeft, marginRight } = props.styles;
 
   // 购买按钮样式
   const buyStyle = computed(() => {
-    let buyNowStyle = props.data.buyNowStyle;
-    if (buyNowStyle.mode == 1) {
+    let btnBuy = props.data.btnBuy;
+    if (btnBuy?.type === 'text') {
       return {
-        background: `linear-gradient(to right, ${buyNowStyle.color1}, ${buyNowStyle.color2})`,
+        background: `linear-gradient(to right, ${btnBuy.bgBeginColor}, ${btnBuy.bgEndColor})`,
       };
     }
 
-    if (buyNowStyle.mode == 2) {
+    if (btnBuy?.type === 'img') {
       return {
         width: '54rpx',
         height: '54rpx',
-        background: `url(${sheep.$url.cdn(buyNowStyle.src)}) no-repeat`,
+        background: `url(${sheep.$url.cdn(btnBuy.imgUrl)}) no-repeat`,
         backgroundSize: '100% 100%',
       };
     }
   });
 
-  const goodsList = ref([]);
+  const productList = ref([]);
   onMounted(async () => {
-    let { data } = await sheep.$api.goods.activity({ activity_id: props.data.activityId });
-    goodsList.value = data;
+    // todo:@owen 与Yudao结构不一致,待重构
+    const { data: activity } = await CombinationApi.getCombinationActivity(props.data.activityId);
+    const { data: spu } = await SpuApi.getSpuDetail(activity.spuId)
+    productList.value = [spu];
   });
 </script>
 

+ 3 - 3
sheep/components/s-image-cube/s-image-cube.vue

@@ -3,10 +3,10 @@
     <view v-for="(item, index) in data.list" :key="index">
       <view
         class="cube-img-wrap"
-        :style="[parseImgStyle(item), { margin: data.space + 'rpx' }]"
+        :style="[parseImgStyle(item), { margin: data.space + 'px' }]"
         @tap="sheep.$router.go(item.url)"
       >
-        <image class="cube-img" :src="sheep.$url.cdn(item.src)" mode="aspectFill"></image>
+        <image class="cube-img" :src="sheep.$url.cdn(item.imgUrl)" mode="aspectFill"></image>
       </view>
     </view>
   </view>
@@ -49,7 +49,7 @@
   const cell = computed(() => {
     return (
       (windowWidth -
-        (props.styles.marginLeft + props.styles.marginRight + props.styles.padding * 2)) /
+        ((props.styles.marginLeft || 0) + (props.styles.marginRight || 0) + (props.styles.padding || 0) * 2)) /
       4
     );
   });

+ 2 - 2
sheep/components/s-line-block/s-line-block.vue

@@ -1,12 +1,12 @@
 <template>
-  <su-subline :color="data.lineColor" :lineStyle="data.style"></su-subline>
+  <su-subline v-bind="data"></su-subline>
 </template>
 
 <script setup>
   const props = defineProps({
     data: {
       type: Object,
-      default() {},
+      default: {},
     },
   });
 </script>

+ 3 - 5
sheep/components/s-richtext-block/s-richtext-block.vue

@@ -15,7 +15,7 @@
 </template>
 <script setup>
   import { reactive, onMounted } from 'vue';
-  import sheep from '@/sheep';
+  import ArticleApi from '@/sheep/api/promotion/article';
   const props = defineProps({
     data: {
       type: Object,
@@ -30,9 +30,7 @@
     content: '',
   });
   onMounted(async () => {
-    const { error, data } = await sheep.$api.data.richtext(props.data.id);
-    if (error === 0) {
-      state.content = data.content;
-    }
+    const { data } = await ArticleApi.getArticle(props.data.id);
+    state.content = data.content;
   });
 </script>

+ 34 - 30
sheep/components/s-seckill-block/s-seckill-block.vue

@@ -1,14 +1,15 @@
 <!-- 装修组件 - 秒杀 -->
 <template>
   <view>
+    <!-- 样式一:三列 - 上图下文 -->
     <view
-      v-if="mode === 1"
+      v-if="layoutType === 'threeCol'"
       class="goods-sm-box ss-flex ss-flex-wrap"
       :style="[{ margin: '-' + data.space + 'rpx' }]"
     >
       <view
-        v-for="item in goodsList"
-        :key="item.id"
+        v-for="product in productList"
+        :key="product.id"
         class="goods-card-box"
         :style="[
           {
@@ -19,49 +20,49 @@
         <s-goods-column
           class="goods-card"
           size="sm"
-          :goodsFields="goodsFields"
+          :goodsFields="data.fields"
           :tagStyle="tagStyle"
-          :data="item"
-          :titleColor="goodsFields.title?.color"
+          :data="product"
+          :titleColor="data.fields.name?.color"
           :topRadius="data.borderRadiusTop"
           :bottomRadius="data.borderRadiusBottom"
           @click="
             sheep.$router.go('/pages/goods/seckill', {
-              id: item.id,
+              id: product.id,
               activity_id: props.data.activityId,
             })
           "
         ></s-goods-column>
       </view>
     </view>
-    <!-- 样式2 -->
-    <view class="goods-box" v-if="mode == 2">
+    <!-- 样式二:一列 - 左图右文 -->
+    <view class="goods-box" v-if="layoutType === 'oneCol'">
       <view
         class="goods-list"
-        v-for="(item, index) in goodsList"
+        v-for="(product, index) in productList"
         :key="index"
         :style="[{ marginBottom: space + 'px' }]"
       >
         <s-goods-column
           class="goods-card"
           size="lg"
-          :includes="goodsFields"
+          :goodsFields="data.fields"
           :tagStyle="tagStyle"
-          :data="item"
-          :titleColor="goodsFields.title?.color"
-          :subTitleColor="goodsFields.subtitle.color"
+          :data="product"
+          :titleColor="data.fields.name?.color"
+          :subTitleColor="data.fields.introduction?.color"
           :topRadius="data.borderRadiusTop"
           :bottomRadius="data.borderRadiusBottom"
           @click="
             sheep.$router.go('/pages/goods/seckill', {
-              id: item.id,
+              id: product.id,
               activity_id: props.data.activityId,
             })
           "
         >
           <template v-slot:cart>
             <button class="ss-reset-button cart-btn" :style="[buyStyle]">
-              {{ buyNowStyle.mode === 1 ? buyNowStyle.text : '' }}
+              {{ btnBuy?.type === 'text' ? btnBuy.text : '' }}
             </button>
           </template>
         </s-goods-column>
@@ -72,14 +73,14 @@
 
 <script setup>
   /**
-   * 秒杀
-   *
-   * @property {Array} list 											- 商品列表
-   *
+   * 秒杀商品列表
    *
+   * @property {Array} list 商品列表
    */
   import { computed, onMounted, ref } from 'vue';
   import sheep from '@/sheep';
+  import SeckillApi from "@/sheep/api/promotion/seckill";
+  import SpuApi from "@/sheep/api/product/spu";
 
   // 接收参数
   const props = defineProps({
@@ -93,32 +94,35 @@
     },
   });
 
-  let { mode, tagStyle, buyNowStyle, goodsFields, space } = props.data;
+  let { layoutType, tagStyle, btnBuy, space } = props.data;
   let { marginLeft, marginRight } = props.styles;
 
   // 购买按钮样式
   const buyStyle = computed(() => {
-    let buyNowStyle = props.data.buyNowStyle;
-    if (buyNowStyle.mode == 1) {
+    let btnBuy = props.data.btnBuy;
+    if (btnBuy?.type === 'text') {
       return {
-        background: `linear-gradient(to right, ${buyNowStyle.color1}, ${buyNowStyle.color2})`,
+        background: `linear-gradient(to right, ${btnBuy.bgBeginColor}, ${btnBuy.bgEndColor})`,
       };
     }
-
-    if (buyNowStyle.mode == 2) {
+    if (btnBuy?.type === 'img') {
       return {
         width: '54rpx',
         height: '54rpx',
-        background: `url(${sheep.$url.cdn(buyNowStyle.src)}) no-repeat`,
+        background: `url(${sheep.$url.cdn(btnBuy.imgUrl)}) no-repeat`,
         backgroundSize: '100% 100%',
       };
     }
   });
 
-  const goodsList = ref([]);
+  // 商品列表
+  const productList = ref([]);
+  // 查询秒杀活动商品
   onMounted(async () => {
-    let { data } = await sheep.$api.goods.activity({ activity_id: props.data.activityId });
-    goodsList.value = data;
+    // todo:@owen 与Yudao结构不一致,待重构
+    const { data: activity } = await SeckillApi.getSeckillActivity(props.data.activityId);
+    const { data: spu } = await SpuApi.getSpuDetail(activity.spuId)
+    productList.value = [spu];
   });
 </script>
 

+ 1 - 1
sheep/components/s-tabbar/s-tabbar.vue

@@ -1,5 +1,5 @@
 <template>
-  <view class="u-page__item" v-if="tabbar?.items.length > 0">
+  <view class="u-page__item" v-if="tabbar?.items?.length > 0">
     <su-tabbar
       :value="path"
       :fixed="true"

+ 55 - 0
sheep/components/s-wallet-card/s-wallet-card.vue

@@ -1,4 +1,5 @@
 <template>
+<<<<<<< HEAD
 	<view class="ss-wallet-menu-wrap ss-flex ss-col-center">
 		<view class="menu-item ss-flex-1 ss-flex-col ss-row-center ss-col-center"
 			@tap="sheep.$router.go('/pages/user/wallet/money')">
@@ -9,6 +10,20 @@
 			<view class="menu-title ss-m-t-28">账户余额</view>
 		</view>
 		<!-- <view class="menu-item ss-flex-1 ss-flex-col ss-row-center ss-col-center"
+=======
+  <view class="ss-wallet-menu-wrap ss-flex ss-col-center">
+    <view
+      class="menu-item ss-flex-1 ss-flex-col ss-row-center ss-col-center"
+      @tap="sheep.$router.go('/pages/user/wallet/money')"
+    >
+      <view class="value-box ss-flex ss-col-bottom">
+        <view class="value-text ss-line-1">{{ userInfo.money || '0.00' }}</view>
+        <view class="unit-text ss-m-l-6">元</view>
+      </view>
+      <view class="menu-title ss-m-t-28">账户余额</view>
+    </view>
+    <!-- <view class="menu-item ss-flex-1 ss-flex-col ss-row-center ss-col-center"
+>>>>>>> 6251ffa9944516e995002e7f11539aef3e1d50de
 			@tap="sheep.$router.go('/pages/user/wallet/commission')">
 			<view class="value-box ss-flex ss-col-bottom">
 				<view class="value-text">{{ userInfo?.commission || '0.00' }}</view>
@@ -16,6 +31,7 @@
 			</view>
 			<view class="menu-title ss-m-t-28">佣金</view>
 		</view> -->
+<<<<<<< HEAD
 		<view class="menu-item ss-flex-1 ss-flex-col ss-row-center ss-col-center"
 			@tap="sheep.$router.go('/pages/user/wallet/score')">
 			<view class="value-box ss-flex ss-col-bottom">
@@ -42,6 +58,45 @@
 			<view class="menu-title ss-m-t-30">我的钱包</view>
 		</view>
 	</view>
+=======
+    <view
+      class="menu-item ss-flex-1 ss-flex-col ss-row-center ss-col-center"
+      @tap="sheep.$router.go('/pages/user/wallet/score')"
+    >
+      <view class="value-box ss-flex ss-col-bottom">
+        <view class="value-text">{{ userInfo.score || 0 }}</view>
+        <view class="unit-text ss-m-l-6">个</view>
+      </view>
+      <view class="menu-title ss-m-t-28">积分</view>
+    </view>
+    <view
+      class="menu-item ss-flex-1 ss-flex-col ss-row-center ss-col-center"
+      @tap="
+        sheep.$router.go('/pages/coupon/list', {
+          type: 'geted',
+        })
+      "
+    >
+      <view class="value-box ss-flex ss-col-bottom">
+        <view class="value-text">{{ numData.coupons_num || 0 }}</view>
+        <view class="unit-text ss-m-l-6">张</view>
+      </view>
+      <view class="menu-title ss-m-t-28">优惠券</view>
+    </view>
+    <view
+      class="menu-item ss-flex-col ss-row-center ss-col-center menu-wallet"
+      @tap="sheep.$router.go('/pages/user/wallet/money')"
+    >
+      <image
+        class="item-icon"
+        :src="sheep.$url.static('/static/img/shop/user/wallet_icon.png')"
+        mode="aspectFit"
+      >
+      </image>
+      <view class="menu-title ss-m-t-30">我的钱包</view>
+    </view>
+  </view>
+>>>>>>> 6251ffa9944516e995002e7f11539aef3e1d50de
 </template>
 
 <script setup>

+ 92 - 71
sheep/hooks/useGoods.js

@@ -2,49 +2,60 @@ import { ref } from 'vue';
 import dayjs from 'dayjs';
 import $url from '@/sheep/url';
 
-// 格式化销量
+/**
+ * 格式化销量
+ * @param {'exact' | string} type 格式类型:exact=精确值,其它=大致数量
+ * @param {number} num 销量
+ * @return {string} 格式化后的销量字符串
+ */
 export function formatSales(type, num) {
-  num = num + '';
-  if (type === 'exact') {
-    return '已售' + num;
-  } else {
-    if (num < 10) {
-      return '销量≤10';
-    } else {
-      let a = Math.pow(10, num.length - 1);
-      return '已售' + parseInt(num / a) * a + '+';
-    }
-  }
+  let prefix = type!=='exact' && num<10 ? '销量': '已售';
+  return formatNum(prefix, type, num)
 }
 
-// 格式化兑换量
+/**
+ * 格式化兑换量
+ * @param {'exact' | string} type 格式类型:exact=精确值,其它=大致数量
+ * @param {number} num 销量
+ * @return {string} 格式化后的销量字符串
+ */
 export function formatExchange(type, num) {
-  num = num + '';
-  if (type === 'exact') {
-    return '已兑换' + num;
-  } else {
-    if (num < 10) {
-      return '已兑换≤10';
-    } else {
-      let a = Math.pow(10, num.length - 1);
-      return '已兑换' + parseInt(num / a) * a + '+';
-    }
-  }
+  return formatNum('已兑换', type, num)
 }
 
-// 格式化库存
+
+/**
+ * 格式化库存
+ * @param {'exact' | any} type 格式类型:exact=精确值,其它=大致数量
+ * @param {number} num 销量
+ * @return {string} 格式化后的销量字符串
+ */
 export function formatStock(type, num) {
-  num = num + '';
+  return formatNum('库存', type, num)
+}
+
+/**
+ * 格式化数字
+ * @param {string} prefix 前缀
+ * @param {'exact' | string} type 格式类型:exact=精确值,其它=大致数量
+ * @param {number} num 销量
+ * @return {string} 格式化后的销量字符串
+ */
+export function formatNum(prefix, type, num) {
+  num = (num || 0);
+  // 情况一:精确数值
   if (type === 'exact') {
-    return '库存' + num;
-  } else {
-    if (num < 10) {
-      return '库存≤10';
-    } else {
-      let a = Math.pow(10, num.length - 1);
-      return '库存 ' + parseInt(num / a) * a + '+';
-    }
+    return prefix + num;
+  }
+  // 情况二:小于等于 10
+  if (num < 10) {
+    return `${prefix}≤10`;
   }
+  // 情况三:大于 10,除第一位外,其它位都显示为0
+  // 例如:100  - 199  显示为 100+
+  //      9000 - 9999 显示为 9000+
+  let pow = Math.pow(10, `${num}`.length - 1);
+  return `${prefix}${(num / pow) * pow}+`;
 }
 
 // 格式化价格
@@ -52,49 +63,54 @@ export function formatPrice(e) {
   return e.length === 1 ? e[0] : e.join('~');
 }
 
-// 格式化商品轮播
-export function formatGoodsSwiper(list) {
-  let swiper = [];
-  list.forEach((item, key) => {
-    if (item.indexOf('.avi') !== -1 || item.indexOf('.mp4') !== -1) {
-      swiper.push({
-        src: $url.cdn(item),
-        type: 'video',
-      });
-    } else {
-      swiper.push({
-        src: $url.cdn(item),
-        type: 'image',
-      });
-    }
+// 视频格式后缀列表
+const VIDEO_SUFFIX_LIST = ['.avi', '.mp4']
+/**
+ * 转换商品轮播的链接列表:根据链接的后缀,判断是视频链接还是图片链接
+ *
+ * @param {string[]} urlList 链接列表
+ * @return {{src: string, type: 'video' | 'image' }[]}  转换后的链接列表
+ */
+export function formatGoodsSwiper(urlList) {
+  return urlList.map((url, key) => {
+    const isVideo = VIDEO_SUFFIX_LIST.some(suffix => url.includes(suffix));
+    const type = isVideo ? 'video' :'image'
+    const src = $url.cdn(url);
+    return { type, src }
   });
-  return swiper;
 }
 
+/**
+ * 格式化订单状态的颜色
+ * @param type 订单类型
+ * @return {string} 颜色的 class 名称
+ */
 export function formatOrderColor(type) {
-  if (
-    type === 'apply_refund' ||
-    type === 'groupon_ing' ||
-    type === 'nocomment' ||
-    type === 'noget' ||
-    type === 'nosend'
-  ) {
-    return 'warning-color';
-  } else if (
-    type === 'closed' ||
-    type === 'groupon_invalid' ||
-    type === 'cancel' ||
-    type === 'refund_agree'
-  ) {
-    return 'danger-color';
-  } else if (type === 'completed') {
-    return 'success-color';
-  } else if (type === 'unpaid') {
-    return 'info-color';
+  switch (type) {
+    case 'apply_refund':
+    case 'groupon_ing':
+    case 'nocomment':
+    case 'noget':
+    case 'nosend':
+      return 'warning-color';
+    case 'closed':
+    case 'groupon_invalid':
+    case 'cancel':
+    case 'refund_agree':
+      return 'danger-color';
+    case 'completed':
+      return 'success-color';
+    case 'unpaid':
+      return 'info-color';
   }
 }
 
-// 计算相隔时间
+/**
+ * 倒计时
+ * @param toTime   截止时间
+ * @param fromTime 起始时间,默认当前时间
+ * @return {{s: string, ms: number, h: string, m: string}} 持续时间
+ */
 export function useDurationTime(toTime, fromTime = '') {
   toTime = getDayjsTime(toTime);
   if (fromTime === '') {
@@ -120,6 +136,11 @@ export function useDurationTime(toTime, fromTime = '') {
   };
 }
 
+/**
+ * 转换为 Dayjs
+ * @param {any} time 时间
+ * @return {dayjs.Dayjs}
+ */
 function getDayjsTime(time) {
   time = time.toString();
   if (time.indexOf('-') > 0) {
@@ -131,7 +152,7 @@ function getDayjsTime(time) {
     return dayjs(parseInt(time));
   }
   if (time.length === 10) {
-    // 'unixtime'
+    // 'unixTime'
     return dayjs.unix(parseInt(time));
   }
 }

+ 1 - 1
sheep/store/app.js

@@ -110,7 +110,7 @@ const adaptTemplate = async (appTemplate) => {
     }
   }
   appTemplate.home = diyTemplate?.data?.home;
-  // appTemplate.user = diyTemplate?.data?.user;
+  appTemplate.user = diyTemplate?.data?.user;
 }
 
 export default app;

+ 3 - 3
sheep/ui/su-coupon/su-coupon.vue

@@ -43,7 +43,7 @@
         </view>
         <view class="ss-m-b-28">
           <view class="title-text ss-m-b-10">{{ props.title }}</view>
-          <view class="surplus-text">仅剩:{{ props.surplus }}张</view>
+          <view class="surplus-text" v-if="props.surplus">仅剩:{{ props.surplus }}张</view>
         </view>
       </view>
       <view class="card-right ss-flex ss-row-center">
@@ -82,7 +82,7 @@
             {{ state.stateMap[props.state] }}
           </button>
         </slot>
-        <view class="surplus-text ss-m-t-24">仅剩:{{ props.surplus }}张</view>
+        <view class="surplus-text ss-m-t-24" v-if="props.surplus">仅剩:{{ props.surplus }}张</view>
       </view>
     </view>
   </view>
@@ -159,7 +159,7 @@
     },
     surplus: {
       type: [Number, String],
-      default: 1000,
+      default: 0,
     },
     type: {
       type: String,

+ 36 - 16
sheep/ui/su-subline/su-subline.vue

@@ -1,42 +1,62 @@
 <template>
-  <view class="ui-subline-wrap" :style="[elStyle]"></view>
+  <view class="wrap" :style="{height: `${height}px`}">
+    <view class="divider" :style="[elStyle]"></view>
+  </view>
 </template>
 
 <script setup>
   /**
-   * 辅助线
-   *
-   * @property {String} width = ['thin', 'medium', 'thick', '10px']				- 线条宽度
-   * @property {String} color = #000 												- 线条颜色
-   * @property {String} style = ['dotted', 'solid', 'double', 'dashed']			- 线条样式,圆点,实线,双线,虚线
-   *
+   * 分割线
    */
 
   import { computed } from 'vue';
 
   // 接收参数
   const props = defineProps({
-    color: {
+    // 线条颜色
+    lineColor: {
       type: String,
       default: '#000',
     },
-    lineStyle: {
+    // 线条样式:'dotted', 'solid', 'double', 'dashed'
+    borderType: {
       type: String,
       default: 'dashed',
     },
-    width: {
-      type: String,
-      default: 'thin',
+    // 线条宽度
+    lineWidth: {
+      type: Number,
+      default: 1,
+    },
+    // 高度
+    height: {
+      type: [Number, String],
+      default: 'auto'
     },
+    // 左右边距:none - 无边距,horizontal - 左右留边
+    paddingType: {
+      type: String,
+      default: 'none'
+    }
   });
 
   const elStyle = computed(() => {
     return {
-      'border-top-width': props.width,
-      'border-top-color': props.color,
-      'border-top-style': props.lineStyle,
+      'border-top-width': `${props.lineWidth}px`,
+      'border-top-color': props.lineColor,
+      'border-top-style': props.borderType,
+      margin: props.paddingType === 'none' ? '0' : '0px 16px'
     };
   });
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.wrap {
+  display: flex;
+  align-items: center;
+
+  .divider {
+    width: 100%;
+  }
+}
+</style>

+ 47 - 0
sheep/util/const.js

@@ -0,0 +1,47 @@
+// ========== MALL - 营销模块 ==========
+
+/**
+ * 优惠类型枚举
+ */
+export const PromotionDiscountTypeEnum = {
+    PRICE: {
+        type: 1,
+        name: '满减'
+    },
+    PERCENT: {
+        type: 2,
+        name: '折扣'
+    }
+}
+
+/**
+ * 优惠劵模板的有限期类型的枚举
+ */
+export const CouponTemplateValidityTypeEnum = {
+    DATE: {
+        type: 1,
+        name: '固定日期可用'
+    },
+    TERM: {
+        type: 2,
+        name: '领取之后可用'
+    }
+}
+
+/**
+ * 营销的商品范围枚举
+ */
+export const PromotionProductScopeEnum = {
+    ALL: {
+        scope: 1,
+        name: '通用劵'
+    },
+    SPU: {
+        scope: 2,
+        name: '商品劵'
+    },
+    CATEGORY: {
+        scope: 3,
+        name: '品类劵'
+    }
+}

+ 79 - 0
sheep/util/index.js

@@ -0,0 +1,79 @@
+import dayjs from "dayjs";
+
+/**
+ * 将一个整数转换为分数保留两位小数
+ * @param {number | string | undefined} num 整数
+ * @return {number} 分数
+ */
+export const formatToFraction = (num) => {
+  if (typeof num === 'undefined') return 0
+  const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
+  return parseFloat((parsedNumber / 100).toFixed(2))
+}
+
+/**
+ * 将一个数转换为 1.00 这样
+ * 数据呈现的时候使用
+ *
+ * @param {number | string | undefined} num 整数
+ * @return {string} 分数
+ */
+export const floatToFixed2 = (num) => {
+  let str = '0.00'
+  if (typeof num === 'undefined') {
+    return str
+  }
+  const f = formatToFraction(num)
+  const decimalPart = f.toString().split('.')[1]
+  const len = decimalPart ? decimalPart.length : 0
+  switch (len) {
+    case 0:
+      str = f.toString() + '.00'
+      break
+    case 1:
+      str = f.toString() + '.0'
+      break
+    case 2:
+      str = f.toString()
+      break
+  }
+  return str
+}
+
+/**
+ * 将一个分数转换为整数
+ *
+ * @param {number | string | undefined} num 分数
+ * @return {number} 整数
+ */
+export const convertToInteger = (num) => {
+  if (typeof num === 'undefined') return 0
+  const parsedNumber = typeof num === 'string' ? parseFloat(num) : num
+  // TODO 分转元后还有小数则四舍五入
+  return Math.round(parsedNumber * 100)
+}
+
+
+
+/**
+ * 时间日期转换
+ * @param {dayjs.ConfigType} date 当前时间,new Date() 格式
+ * @param {string} format 需要转换的时间格式字符串
+ * @description format 字符串随意,如 `YYYY-mm、YYYY-mm-dd`
+ * @description format 季度:"YYYY-mm-dd HH:MM:SS QQQQ"
+ * @description format 星期:"YYYY-mm-dd HH:MM:SS WWW"
+ * @description format 几周:"YYYY-mm-dd HH:MM:SS ZZZ"
+ * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
+ * @returns {string} 返回拼接后的时间字符串
+ */
+export function formatDate(date, format) {
+  // 日期不存在,则返回空
+  if (!date) {
+    return ''
+  }
+  // 日期存在,则进行格式化
+  if (format === undefined) {
+    format = 'YYYY-MM-DD HH:mm:ss'
+  }
+  return dayjs(date).format(format)
+}