绿满眶商城微信小程序-uniapp
peng
2025-07-21 408d93a7fed0403381400ce2fc028819bb203eec
解决主包过大问题
3个文件已修改
6个文件已添加
1248 ■■■■■ 已修改文件
pages.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/commodity-square/commoditySquare.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/product/goods.vue 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/subComponents/m-goods-list/README.md 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/subComponents/m-goods-list/base-list.vue 277 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/subComponents/m-goods-list/common.vue 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/subComponents/m-goods-list/list.vue 343 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/subComponents/m-goods-list/promotion.vue 171 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages/subComponents/popups/popups.vue 346 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pages.json
@@ -1026,7 +1026,8 @@
            "componentPlaceholder": {
              "u-icon": "view",
              "u-navbar": "view",
              "u-popup": "view"
              "u-popup": "view",
              "popups":"view"
            }
          }
        },
@@ -1714,6 +1715,19 @@
          {
              "navigationBarTitleText" : "分包占位"
          }
      },{
          "path" : "popups/popups",
          "style" :
          {
              "componentPlaceholder": {
                "u-modal": "view",
                "u-tabs": "view",
                "u-image": "view",
                "u-search": "view",
                "u-icon": "view",
                "u-loadmore": "view"
              }
          }
      }
      ]
    },
pages/commodity-square/commoditySquare.vue
@@ -17,7 +17,7 @@
            </view>
            <view class="goodsInfos">
                <scroll-view :scroll-y="true" :show-scrollbar="false">
                <scroll-view :scroll-y="true" :show-scrollbar="false" style="height: 100%;">
                    <view class="goodsItem" v-for="item in goodsList" :key="item.id"
                        @click.prevent="goToGoodsInfo(item.id)"
pages/product/goods.vue
@@ -184,24 +184,24 @@
          <Evaluation id="main5" :goodsDetail="goodsDetail" />
          <!-- 店铺推荐 -->
          <storeLayout id="main7" :storeDetail="storeDetail" :goodsDetail="goodsDetail" :res="recommendList" />
          <!-- <storeLayout id="main7" :storeDetail="storeDetail" :goodsDetail="goodsDetail" :res="recommendList" /> -->
          <!-- 宝贝详情 -->
          <GoodsIntro id="main9" :res="goodsDetail" :goodsParams="goodsParams" :goodsId="goodsDetail.goodsId"
            v-if="goodsDetail.id" />
          <!-- 宝贝推荐 -->
          <GoodsRecommend id="main11" :res="likeGoodsList" />
          <!-- <GoodsRecommend id="main11" :res="likeGoodsList" /> -->
        </view>
      </scroll-view>
      <view class="page-bottom mp-iphonex-bottom" id="pageBottom">
        <view class="icon-btn">
          <view class="icon-btn-item" @click="navigateToStore(goodsDetail.storeId)">
<!--          <view class="icon-btn-item" @click="navigateToStore(goodsDetail.storeId)">
            <u-icon size="34" class="red" name="home-fill"></u-icon>
            <view class="red icon-btn-name">店铺</view>
          </view>
          </view> -->
          <view class="icon-btn-item" @click="linkMsgDetail()">
            <u-icon size="34" name="kefu-ermai"></u-icon>
            <view class="icon-btn-name">客服</view>
@@ -295,14 +295,14 @@
import PromotionAssembleListLayout from "./product/promotion/-promotion-assemble-list"; //拼团用户列表
import PromotionCoupon from "./product/promotion/-promotion-coupon"; //优惠券组件
import GoodsIntro from "./product/goods/-goods-intro"; //商品介绍组件
import GoodsRecommend from "./product/goods/-goods-recommend"; //宝贝推荐
// import GoodsRecommend from "./product/goods/-goods-recommend"; //宝贝推荐
import storeLayout from "./product/shop/-shop"; //店铺组件
import Evaluation from "./product/evaluation/-evaluation"; //评价组件
import GoodsSwiper from "./product/goods/-goods-swiper"; //轮播图组件
import popupGoods from "@/pages/product/m-buy/goods.vue"; //购物车商品的模块
import popupAddress from "./product/popup/address"; //地址选择模块
import shares from "@/pages/product/m-share/index.vue"; //分享
import popups from "@/pages/product/popups/popups.vue"; //气泡框
import popups from "@/pages/subComponents/popups/popups.vue"; //气泡框
import takeDownFormSaleGoods from "@/pages/product/m-take-down-sale-goods/index"; //下架框
import setup from "./product/popup/popup";
    import {
@@ -318,7 +318,6 @@
    PromotionAssembleListLayout,
    PromotionCoupon,
    GoodsIntro,
    GoodsRecommend,
    storeLayout,
    Evaluation,
    GoodsSwiper,
pages/subComponents/m-goods-list/README.md
New file
@@ -0,0 +1,11 @@
## 商品列表展示
### OBJECT 参数说明
| 属性        | 说明                                                       | 类型    | 必填 |
| ----------- | ---------------------------------------------------------- | ------- | ---- |
| `res`       | 显示数据                                                   | Array   | 是   |
| `type`      | 商品展示类型 oneColumns twoColumns  ,默认展示一行两列商品 | String  | 否   |
| `storeName` | 是否展示店铺名称,默认展示                                 | Boolean | 否   |
| `keywords` | 高亮展示搜索内容                                 | String | 否   |
pages/subComponents/m-goods-list/base-list.vue
New file
@@ -0,0 +1,277 @@
<template>
  <div>
    <!-- 一行两列商品展示 -->
    <view class="goods-list" v-if="type == 'twoColumns'">
      <view v-for="(item, index) in res" :key="index" class="goods-item">
        <view class="image-wrapper" @click="navigateToDetailPage(item)">
          <u-image
            :src="item.thumbnail"
            width="100%"
            height="330rpx"
           mode="aspectFit"
          >
            <u-loading slot="loading"></u-loading>
          </u-image>
        </view>
        <view class="goods-detail">
          <div
            class="title clamp"
            v-html="lightSearchStr(keyword, item.goodsName)"
            @click="navigateToDetailPage(item)"
          ></div>
          <view class="price-box" @click="navigateToDetailPage(item)">
            <div class="price" v-if="item.price != undefined">
              ¥<span
                >{{
                  $options.filters.goodsFormatPrice(item.price)[0]
                }} </span
              >.{{ $options.filters.goodsFormatPrice(item.price)[1] }}
            </div>
          </view>
          <div class="count-config" @click="navigateToDetailPage(item)">
            <span>已售 {{ item.buyCount || "0" }}</span>
            <span>{{ item.commentNum || "0" }}条评论</span>
          </div>
          <div
            class="store-seller-name"
            v-if="storeName"
            @click="navigateToStoreDetailPage(item)"
          >
            <div class="text-hidden">
              <u-tag
                style="margin-right: 10rpx"
                size="mini"
                mode="dark"
                v-if="item.selfOperated"
                text="自营"
                type="error"
              />
              <span>{{ item.storeName || "暂无" }}</span>
            </div>
            <span>
              <u-icon name="arrow-right"></u-icon>
            </span>
          </div>
        </view>
      </view>
    </view>
     <!-- 一行一列商品展示 -->
    <div v-if="type == 'oneColumns'">
      <div  v-for="(item, index) in res" :key="index" class="goods-row">
        <div class="flex goods-col">
          <div class="goods-img" @click="navigateToDetailPage(item)">
            <u-image
              width="230rpx"
              border-radius="16"
              height="230rpx"
                            mode="aspectFit"
              :src="item.goodsImage || item.thumbnail"
            >
              <u-loading slot="loading"></u-loading>
            </u-image>
          </div>
          <div class="goods-detail">
            <div class="title clamp3" @click="navigateToDetailPage(item)">
              {{ item.goodsName }}
            </div>
            <view class="price-box" @click="navigateToDetailPage(item)">
              <div class="price" v-if="item.price != undefined">
                ¥<span
                  >{{ $options.filters.goodsFormatPrice(item.price)[0] }} </span
                >.{{ $options.filters.goodsFormatPrice(item.price)[1] }}
              </div>
            </view>
            <div class="promotion" @click="navigateToDetailPage(item)">
              <div v-if="item.salesModel == 'WHOLESALE'">
                <span>批</span>
              </div>
              <div
                v-for="(promotionItem, promotionIndex) in getPromotion(item)"
                :key="promotionIndex"
              >
                <span v-if="promotionItem.indexOf('COUPON') != -1">劵</span>
                <span v-if="promotionItem.indexOf('FULL_DISCOUNT') != -1"
                  >满减</span
                >
                <span v-if="promotionItem.indexOf('SECKILL') != -1">秒杀</span>
              </div>
            </div>
            <div
              style="overflow: hidden"
              @click="navigateToDetailPage(item)"
              class="count-config"
            >
              <span style="float: left; font-size: 22rpx"
                >已售 {{ item.buyCount || "0" }}</span
              >
              <span style="float: right; font-size: 22rpx"
                >{{ item.commentNum || "0" }}条评论</span
              >
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import commonTpl from "@/pages/product/m-goods-list/common.vue";
export default {
  data() {
    return {
      lightColor: this.$mainColor,
    };
  },
  mixins: [commonTpl],
  props: {
    // 展示的类型
    type:{
        type:String,
        default:"oneColumns"
    },
    // 遍历的数据
    res: {
      type: Array,
      default: () => {
        return [];
      },
    },
  },
  methods: {
    // 跳转到商品详情
    navigateToDetailPage(item) {
      uni.navigateTo({
        url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
      });
    },
  },
};
</script>
<style lang="scss" scoped>
.goods-list {
  display: flex;
  flex-wrap: wrap;
  margin: 10rpx 20rpx 284rpx;
  width: 100%;
  > .goods-item {
    background-color: #ffffff;
    display: flex;
    border-radius: 16rpx;
    flex-direction: column;
    width: calc(50% - 30rpx);
    margin-bottom: 20rpx;
    padding-bottom: 20rpx;
    &:nth-child(2n + 1) {
      margin-right: 20rpx;
    }
    .image-wrapper {
      width: 100%;
      height: 330rpx;
      border-radius: 16rpx 16rpx 0 0;
      overflow: hidden;
      padding: 0;
    }
  }
  .count-config,
  .store-seller-name {
    font-size: $font-sm;
  }
  .text-hidden {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}
.goods-row {
  background: #fff;
  padding: 16rpx;
  > .goods-col {
    display: flex;
    > .goods-img {
      overflow: hidden;
      flex: 4;
    }
    > .goods-detail {
      flex: 7;
    }
  }
}
.goods-detail {
  margin: 0 20rpx;
  > .title {
    font-size: $font-base;
    color: $font-color-dark;
    line-height: 1.5;
    height: 86rpx;
    padding: 10rpx 0 0;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
  }
  .promotion {
    margin-top: 4rpx;
    display: flex;
    div {
      span {
        font-size: 24rpx;
        color: $light-color;
        margin-right: 10rpx;
        padding: 0 4rpx;
        border-radius: 2rpx;
      }
    }
  }
  .store-seller-name {
    color: #666;
    overflow: hidden;
    display: flex;
    justify-content: space-between;
  }
  .count-config {
    padding: 5rpx 0;
    color: #666;
    display: flex;
    font-size: 24rpx;
    justify-content: space-between;
  }
  > .price-box {
    margin-top: 10rpx;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-right: 10rpx;
    font-size: 24rpx;
    color: $font-color-light;
    > .price {
      font-size: 26rpx;
      line-height: 1;
      color: $main-color;
      font-weight: bold;
      /deep/ span:nth-of-type(1) {
        font-size: 38rpx;
      }
    }
  }
}
</style>
pages/subComponents/m-goods-list/common.vue
New file
@@ -0,0 +1,69 @@
<template>
</template>
<script>
    export default {
        methods: {
            // 高亮显示搜索内容
            lightSearchStr(keyword, str) {
                if (!keyword) {
                    return str
                } else {
                    let unicodes = '';
                    for (let i of Array.from(keyword)) {
                        unicodes += this.unicode(i) + "|"
                    }
                    const rule = '(' + unicodes + ')'
                    const reg = new RegExp(rule, 'gi');
                    return str ? str.replace(reg, matchValue =>
                        `<span style="color:${this.lightColor}">${matchValue}</span>`
                    ) : ''
                }
            },
            //  转换为unicode
            unicode(str) {
                var value = '';
                for (var i = 0; i < str.length; i++) {
                    value += '\\u' + this.left_zero_4(parseInt(str.charCodeAt(i)).toString(16));
                }
                return value;
            },
            left_zero_4(str) {
                if (str != null && str != '' && str != 'undefined') {
                    if (str.length == 2) {
                        return '00' + str;
                    }
                }
                return str;
            },
            // 数据去重一下 只显示一次 减免 劵 什么的
            getPromotion(item) {
                if (item.promotionMap) {
                    let array = [];
                    Object.keys(item.promotionMap).forEach((child) => {
                        if (!array.includes(child.split("-")[0])) {
                            array.push(child.split("-")[0]);
                        }
                    });
                    return array;
                }
            },
            // 跳转到商品详情
            navigateToDetailPage(item) {
                uni.navigateTo({
                    url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
                });
            },
            // 跳转地址
            navigateToStoreDetailPage(item) {
                uni.navigateTo({
                    url: `/pages/product/shopPage?id=${item.storeId}`,
                });
            },
        },
    }
</script>
<style lang='scss' scoped>
</style>
pages/subComponents/m-goods-list/list.vue
New file
@@ -0,0 +1,343 @@
<template>
    <view>
        <!-- 一行两列商品展示 -->
        <view class="goods-list" v-if="type == 'twoColumns'">
            <view v-for="(item, index) in res" :key="index" class="goods-item">
                <view class="image-wrapper" @click="navigateToDetailPage(item)">
                    <u-image :src="item.thumbnail" width="100%" height='330rpx' mode="aspectFit">
                        <u-loading slot="loading"></u-loading>
                    </u-image>
                </view>
                <view class="goods-detail">
                    <div class="title clamp" v-html="lightSearchStr(keyword,item.goodsName)"
                        @click="navigateToDetailPage(item)">
                    </div>
                    <view class="price-box" @click="navigateToDetailPage(item)">
                        <div class="price" v-if="item.price!=undefined">
                            ¥<span>{{ $options.filters.goodsFormatPrice(item.price )[0] }} </span>.{{
                $options.filters.goodsFormatPrice(item.price )[1]
              }}
                        </div>
                    </view>
                    <div class="promotion" @click="navigateToDetailPage(item)">
                        <div v-if="item.salesModel == 'WHOLESALE'">
                            <span>批</span>
                        </div>
                        <div v-for="(promotionItem,promotionIndex) in  getPromotion(item)" :key="promotionIndex">
                            <span v-if="promotionItem.indexOf('COUPON') != -1">劵</span>
                            <span v-if="promotionItem.indexOf('FULL_DISCOUNT') != -1">满减</span>
                            <span v-if="promotionItem.indexOf('SECKILL') != -1">秒杀</span>
                        </div>
                    </div>
                    <div class="count-config" @click="navigateToDetailPage(item)">
                        <span>已售 {{ item.buyCount || "0" }}</span>
                        <span>{{ item.commentNum || "0" }}条评论</span>
                    </div>
                    <div class="store-seller-name" v-if="storeName" @click="navigateToStoreDetailPage(item)">
                        <div class="text-hidden">
                            <u-tag style="margin-right: 10rpx" size="mini" mode="dark" v-if="item.selfOperated"
                                text="自营" type="error" />
                            <span>{{ item.storeName || "暂无" }}</span>
                        </div>
                        <span>
                            <u-icon name="arrow-right"></u-icon>
                        </span>
                    </div>
                </view>
            </view>
        </view>
        <!-- 一行一列商品展示 -->
        <div v-if="type == 'oneColumns'"  class="goods-one-row">
            <div v-for="(item, index) in res" :key="index" class="goods-row">
                <div class="flex goods-col">
                    <div class="goods-img" @click="navigateToDetailPage(item)">
                        <u-image width="230rpx" mode="aspectFit" border-radius='16' height="230rpx" :src="item.thumbnail">
                            <u-loading slot="loading"></u-loading>
                        </u-image>
                    </div>
                    <div class="goods-detail">
                        <div class="title clamp3" @click="navigateToDetailPage(item)">{{ item.goodsName }}</div>
                        <view class="price-box" @click="navigateToDetailPage(item)">
                            <div class="price" v-if="item.price!=undefined">
                                ¥<span>{{ $options.filters.goodsFormatPrice(item.price )[0] }} </span>.{{
                        $options.filters.goodsFormatPrice(item.price )[1]
                      }}
                            </div>
                        </view>
                        <div class="promotion" @click="navigateToDetailPage(item)">
                            <div v-if="item.salesModel == 'WHOLESALE'">
                                <span>批</span>
                            </div>
                            <div v-for="(promotionItem,promotionIndex) in  getPromotion(item)" :key="promotionIndex">
                                <span v-if="promotionItem.indexOf('COUPON') != -1">劵</span>
                                <span v-if="promotionItem.indexOf('FULL_DISCOUNT') != -1">满减</span>
                                <span v-if="promotionItem.indexOf('SECKILL') != -1">秒杀</span>
                            </div>
                        </div>
                        <div style="overflow: hidden" @click="navigateToDetailPage(item)" class="count-config">
                            <span style="float: left; font-size: 22rpx">已售 {{ item.buyCount || '0' }}</span>
                            <span style="float: right; font-size: 22rpx">{{ item.commentNum || '0' }}条评论</span>
                        </div>
                        <div style="overflow: hidden" @click="navigateToStoreDetailPage(item)" class="count-config">
                            <div class="text-hidden" v-if="storeName">
                                <u-tag style="margin-right: 10rpx" size="mini" mode="dark" v-if="item.selfOperated"
                                    text="自营" type="error" />
                                <span class="line1-store-name">{{ item.storeName }}</span>
                                <span class="to-store">进店<u-icon size="24" name="arrow-right" color="#666"></u-icon>
                                </span>
                            </div>
                            <span>
                                <u-icon name="arrow-right" color="#c5c5c5"></u-icon>
                            </span>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </view>
</template>
<script>
    import '@/components/uview-components/uview-ui';
    import commonTpl from '@/pages/product/m-goods-list/common.vue'
    export default {
        data() {
            return {
                lightColor: this.$mainColor
            }
        },
        mixins: [commonTpl],
        props: {
            // 遍历的数据
            res: {
                type: Array,
                default: () => {
                    return []
                }
            },
            // 一行两列还是一行一列显示
            type: {
                type: String,
                default: 'twoColumns',
                validator() {
                    return ['twoColumns', 'oneColumns']
                }
            },
            storeName: {
                type: Boolean,
                default: true
            },
            keyword: {
                type: null,
                default: ''
            }
        },
        watch: {
            keyword(val) {
                if (val) {
                    this.lightSearchStr(val)
                }
            }
        },
        methods: {
            // 高亮显示搜索内容
            lightSearchStr(keyword, str) {
                if (!keyword) {
                    return str
                } else {
                    let unicodes = '';
                    for (let i of Array.from(keyword)) {
                        unicodes += this.unicode(i) + "|"
                    }
                    const rule = '(' + unicodes + ')'
                    const reg = new RegExp(rule, 'gi');
                    return str ? str.replace(reg, matchValue =>
                        `<span style="color:${this.lightColor}">${matchValue}</span>`
                    ) : ''
                }
            },
            //  转换为unicode
            unicode(str) {
                var value = '';
                for (var i = 0; i < str.length; i++) {
                    value += '\\u' + this.left_zero_4(parseInt(str.charCodeAt(i)).toString(16));
                }
                return value;
            },
            left_zero_4(str) {
                if (str != null && str != '' && str != 'undefined') {
                    if (str.length == 2) {
                        return '00' + str;
                    }
                }
                return str;
            },
            // 数据去重一下 只显示一次 减免 劵 什么的
            getPromotion(item) {
                if (item ? item.promotionMap : item.promotionMap) {
                    const fieldList = item ? item.promotionMap : item.promotionMap
                    let array = [];
                    Object.keys(fieldList).forEach((child) => {
                        if (!array.includes(child.split("-")[0])) {
                            array.push(child.split("-")[0]);
                        }
                    });
                    return array;
                }
            },
            // 跳转到商品详情
            navigateToDetailPage(item) {
                uni.navigateTo({
                    url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
                });
            },
            // 跳转地址
            navigateToStoreDetailPage(item) {
                uni.navigateTo({
                    url: `/pages/product/shopPage?id=${item.storeId}`,
                });
            },
        }
    }
</script>
<style lang='scss' scoped>
        .goods-one-row{
            padding-bottom: 250rpx;
        }
        /* 商品列表 */
        .goods-list {
            display: flex;
            flex-wrap: wrap;
            margin: 10rpx 20rpx 284rpx;
            width: 100%;
            >.goods-item {
                background-color: #ffffff;
                display: flex;
                border-radius: 16rpx;
                flex-direction: column;
                width: calc(50% - 30rpx);
                margin-bottom: 20rpx;
                padding-bottom: 20rpx;
                &:nth-child(2n + 1) {
                    margin-right: 20rpx;
                }
                .image-wrapper {
                    width: 100%;
                    height: 330rpx;
                    border-radius: 16rpx 16rpx 0 0;
                    overflow: hidden;
                    padding: 0;
                }
            }
            .count-config,
            .store-seller-name {
                font-size: $font-sm;
            }
            .text-hidden {
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
            }
        }
        .goods-row {
            background: #fff;
            padding: 16rpx;
            >.goods-col {
                display: flex;
                >.goods-img {
                    overflow: hidden;
                    flex: 4;
                }
                >.goods-detail {
                    flex: 7;
                }
            }
        }
        .goods-detail {
            margin: 0 20rpx;
            >.title {
                font-size: $font-base;
                color: $font-color-dark;
                line-height: 1.5;
                height: 86rpx;
                padding: 10rpx 0 0;
                display: -webkit-box;
                -webkit-box-orient: vertical;
                -webkit-line-clamp: 2;
                overflow: hidden;
            }
            .promotion {
                margin-top: 4rpx;
                display: flex;
                div {
                    span {
                        font-size: 24rpx;
                        color: $light-color;
                        margin-right: 10rpx;
                        padding: 0 4rpx;
                        border-radius: 2rpx;
                    }
                }
            }
            .store-seller-name {
                color: #666;
                overflow: hidden;
                display: flex;
                justify-content: space-between;
            }
            .count-config {
                padding: 5rpx 0;
                color: #666;
                display: flex;
                font-size: 24rpx;
                justify-content: space-between;
            }
            >.price-box {
                margin-top: 10rpx;
                display: flex;
                align-items: center;
                justify-content: space-between;
                padding-right: 10rpx;
                font-size: 24rpx;
                color: $font-color-light;
                >.price {
                    font-size: 26rpx;
                    line-height: 1;
                    color: $main-color;
                    font-weight: bold;
                    /deep/ span:nth-of-type(1) {
                        font-size: 38rpx;
                    }
                }
            }
        }
</style>
pages/subComponents/m-goods-list/promotion.vue
New file
@@ -0,0 +1,171 @@
<template>
    <div>
        <div v-for="(item, index) in res" :key="index" class="goods-row" @click="navigateToDetailPage(item)">
            <div class="flex goods-col">
                <div class="goods-img">
                    <u-image width="230rpx" mode="aspectFit" border-radius='16' height="230rpx" :src="item.goodsImage || item.thumbnail">
                        <u-loading slot="loading"></u-loading>
                    </u-image>
                </div>
                <div class="goods-detail">
                    <div class="title clamp3">{{ item.goodsName }}</div>
                    <div class='flex flex-a-c flex-j-sb'>
                        <view class="price-box">
                            <!-- 秒杀 / 拼团 -->
                            <div class="price" v-if="!type && item.price!=undefined">
                                ¥<span>{{ $options.filters.goodsFormatPrice(item.price )[0] }} </span>.{{
                                        $options.filters.goodsFormatPrice(item.price )[1]
                                    }}
                            </div>
                            <!-- 砍价 -->
                            <div class="price" v-if="type && item.purchasePrice!=undefined">
                                最低:
                                ¥<span>{{ $options.filters.goodsFormatPrice(item.purchasePrice )[0] }} </span>.{{
                                        $options.filters.goodsFormatPrice(item.purchasePrice )[1]
                                    }}
                            </div>
                            <!-- 兜底策略如果金额是0 -->
                            <div class="price" v-if="!item.price && !type">
                                ¥<span>0 </span>.00
                            </div>
                        </view>
                        <div>
                            <image class='buy' :src="buy"></image>
                        </div>
                    </div>
                    <div class='count-config' v-if="!type">
                        <span>即将恢复{{ item.originalPrice}}元</span>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
    import '@/components/uview-components/uview-ui'
    import commonTpl from '@/pages/product/m-goods-list/common.vue'
    export default {
        data() {
            return {
                lightColor: this.$mainColor,
                buy: require('@/pages/subComponents/static/buy.png')
            }
        },
        mixins: [commonTpl],
        props: {
            // 遍历的数据
            res: {
                type: Array,
                default: () => {
                    return []
                }
            },
            type:{
                type:null,
                default:""
            }
        },
        methods: {
            // 跳转到商品详情
            navigateToDetailPage(item) {
                if(this.type == 'kanJia'){
                     uni.navigateTo({
                        url: `/pages/promotion/bargain/detail?id=${item.id}`,
                    });
                    return
                }
                uni.navigateTo({
                    url: `/pages/product/goods?id=${item.skuId}&goodsId=${item.goodsId}`,
                });
            },
        }
    }
</script>
<style lang='scss' scoped>
    .buy {
        width: 152rpx;
        height: 108rpx;
    }
    .flex-j-sb {
        width: 100%;
    }
    .goods-row {
        background: #fff;
        padding: 16rpx;
        >.goods-col {
            display: flex;
            >.goods-img {
                overflow: hidden;
                flex: 4;
            }
            >.goods-detail {
                flex: 7;
            }
        }
    }
    .goods-detail {
        margin: 0 20rpx;
        >.title {
            font-size: $font-base;
            color: $font-color-dark;
            line-height: 1.5;
            height: 86rpx;
            padding: 10rpx 0 0;
            display: -webkit-box;
            -webkit-box-orient: vertical;
            -webkit-line-clamp: 2;
            overflow: hidden;
        }
        .promotion {
            margin-top: 4rpx;
            display: flex;
            div {
                span {
                    font-size: 24rpx;
                    color: $light-color;
                    margin-right: 10rpx;
                    padding: 0 4rpx;
                    border-radius: 2rpx;
                }
            }
        }
        .count-config {
            padding: 5rpx 0;
            color: #666;
            display: flex;
            font-size: 24rpx;
            letter-spacing:2rpx;
            padding-left: 10rpx;
        }
    }
    .price-box {
        margin-top: 10rpx;
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding-right: 10rpx;
        font-size: 24rpx;
        color: $font-color-light;
        >.price {
            font-size: 26rpx;
            line-height: 1;
            color: $main-color;
            font-weight: bold;
            /deep/ span:nth-of-type(1) {
                font-size: 48rpx;
            }
        }
    }
</style>
pages/subComponents/popups/popups.vue
New file
@@ -0,0 +1,346 @@
<template>
  <view class="shadow" :class="!show?'':'shadow-show'" :style="{backgroundColor:show?maskBg:'rgba(0,0,0,0)'}" @tap="tapMask">
    <view class="popups" :class="[theme]" :style="{top: popupsTop ,left: popupsLeft,flexDirection:direction}">
      <text :class="dynPlace" :style="{width:'0px',height:'0px'}" v-if="triangle"></text>
      <view v-for="(item,index) in popData" :key="index" @tap.stop="tapItem(item)" class="itemChild view" :class="[direction=='row'?'solid-right':'solid-bottom',item.disabled?'disabledColor':'']">
        <u-icon size="35" :name="item.icon" v-if="item.icon"></u-icon><span class="title">{{item.title}}</span>
      </view>
      <slot></slot>
    </view>
  </view>
</template>
<script>
import '@/components/uview-components/uview-ui';
export default {
  props: {
    maskBg: {
      type: String,
      default: "rgba(0,0,0,0)",
    },
    placement: {
      type: String,
      default: "default", //default top-start top-end bottom-start bottom-end
    },
    direction: {
      type: String,
      default: "column", //column row
    },
    x: {
      type: Number,
      default: 0,
    },
    y: {
      type: Number,
      default: 0,
    },
    value: {
      type: Boolean,
      default: false,
    },
    popData: {
      type: Array,
      default: () => [],
    },
    theme: {
      type: String,
      default: "light", //light dark
    },
    dynamic: {
      type: Boolean,
      default: false,
    },
    gap: {
      type: Number,
      default: 20,
    },
    triangle: {
      type: Boolean,
      default: true,
    },
  },
  data() {
    return {
      popupsTop: "0rpx",
      popupsLeft: "0rpx",
      show: false,
      dynPlace: "",
    };
  },
  mounted() {
    this.popupsPosition();
  },
  methods: {
    tapMask() {
      this.$emit("input", !this.value);
    },
    tapItem(item) {
      if (item.disabled) return;
      this.$emit("tapPopup", item);
      this.$emit("input", !this.value);
    },
    getStatusBar() {
      let promise = new Promise((resolve, reject) => {
        uni.getSystemInfo({
          success: function (e) {
            let customBar;
            // #ifdef H5
            customBar = e.statusBarHeight + e.windowTop;
            // #endif
            resolve(customBar);
          },
        });
      });
      return promise;
    },
    async popupsPosition() {
      let statusBar = await this.getStatusBar();
      let promise = new Promise((resolve, reject) => {
        let popupsDom = uni.createSelectorQuery().in(this).select(".popups");
        popupsDom
          .fields(
            {
              size: true,
            },
            (data) => {
              let width = data.width;
              let height = data.height;
              let y = this.dynamic
                ? this.dynamicGetY(this.y, this.gap)
                : this.transformRpx(this.y);
              let x = this.dynamic
                ? this.dynamicGetX(this.x, this.gap)
                : this.transformRpx(this.x);
              // #ifdef H5
              y = this.dynamic
                ? this.y + statusBar
                : this.transformRpx(this.y + statusBar);
              // #endif
              this.dynPlace =
                this.placement == "default"
                  ? this.getPlacement(x, y)
                  : this.placement;
              switch (this.dynPlace) {
                case "top-start":
                  this.popupsTop = `${y + 9}rpx`;
                  this.popupsLeft = `${x - 15}rpx`;
                  break;
                case "top-end":
                  this.popupsTop = `${y + 9}rpx`;
                  this.popupsLeft = `${x + 15 - width}rpx`;
                  break;
                case "bottom-start":
                  this.popupsTop = `${y - 18 - height}rpx`;
                  this.popupsLeft = `${x - 15}rpx`;
                  break;
                case "bottom-end":
                  this.popupsTop = `${y - 9 - height}rpx`;
                  this.popupsLeft = `${x + 15 - width}rpx`;
                  break;
              }
              resolve();
            }
          )
          .exec();
      });
      return promise;
    },
    getPlacement(x, y) {
      let width = uni.getSystemInfoSync().windowWidth;
      let height = uni.getSystemInfoSync().windowHeight;
      if (x > width / 2 && y > height / 2) {
        return "bottom-end";
      } else if (x < width / 2 && y < height / 2) {
        return "top-start";
      } else if (x > width / 2 && y < height / 2) {
        return "top-end";
      } else if (x < width / 2 && y > height / 2) {
        return "bottom-start";
      } else if (x > width / 2) {
        return "top-end";
      } else {
        return "top-start";
      }
    },
    dynamicGetY(y, gap) {
      let height = uni.getSystemInfoSync().windowHeight;
      y = y < gap ? gap : y;
      y = height - y < gap ? height - gap : y;
      return y;
    },
    dynamicGetX(x, gap) {
      let width = uni.getSystemInfoSync().windowWidth;
      x = x < gap ? gap : x;
      x = width - x < gap ? width - gap : x;
      return x;
    },
    transformRpx(params) {
      return (params * uni.getSystemInfoSync().screenWidth) / 375;
    },
  },
  watch: {
    value: {
      immediate: true,
      handler: async function (newVal, oldVal) {
        if (newVal) await this.popupsPosition();
        this.show = newVal;
      },
    },
    placement: {
      immediate: true,
      handler(newVal, oldVal) {
        this.dynPlace = newVal;
      },
    },
  },
};
</script>
<style lang="scss" scoped>
.title {
  margin-left: 20rpx;
}
.shadow {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  height: 100%;
  z-index: 9999;
  transition: background 0.3s ease-in-out;
  visibility: hidden;
  &.shadow-show {
    visibility: visible;
  }
}
.popups {
  position: absolute;
  padding: 20rpx;
  border-radius: 5px;
  display: flex;
  .view {
    display: flex;
    align-items: center;
    padding: 15rpx 10rpx;
    font-size: 25rpx;
  }
  .image {
    display: inline-block;
    vertical-align: middle;
    width: 40rpx;
    height: 40rpx;
    margin-right: 20rpx;
  }
}
.dark {
  background-color: #4c4c4c;
  color: #fff;
  .top-start:after {
    content: "";
    position: absolute;
    top: -18rpx;
    left: 10rpx;
    border-width: 0 20rpx 20rpx;
    border-style: solid;
    border-color: transparent transparent #4c4c4c;
  }
  .top-end:after {
    content: "";
    position: absolute;
    top: -18rpx;
    right: 10rpx;
    border-width: 0 20rpx 20rpx;
    border-style: solid;
    border-color: transparent transparent #4c4c4c;
  }
  .bottom-start:after {
    content: "";
    position: absolute;
    bottom: -18rpx;
    left: 10rpx;
    border-width: 20rpx 20rpx 0;
    border-style: solid;
    border-color: #4c4c4c transparent transparent;
  }
  .bottom-end:after {
    content: "";
    position: absolute;
    bottom: -18rpx;
    right: 10rpx;
    border-width: 20rpx 20rpx 0;
    border-style: solid;
    border-color: #4c4c4c transparent transparent;
  }
  .disabledColor {
    color: #c5c8ce;
  }
}
.light {
  color: #515a6e;
  box-shadow: 0upx 0upx 30upx rgba(0, 0, 0, 0.2);
  background: #fff;
  .top-start:after {
    content: "";
    position: absolute;
    top: -18rpx;
    left: 10rpx;
    border-width: 0 20rpx 20rpx;
    border-style: solid;
    border-color: transparent transparent #fff;
  }
  .top-end:after {
    content: "";
    position: absolute;
    top: -18rpx;
    right: 10rpx;
    border-width: 0 20rpx 20rpx;
    border-style: solid;
    border-color: transparent transparent #fff;
  }
  .bottom-start:after {
    content: "";
    position: absolute;
    bottom: -18rpx;
    left: 10rpx;
    border-width: 20rpx 20rpx 0;
    border-style: solid;
    border-color: #fff transparent transparent;
  }
  .bottom-end:after {
    content: "";
    position: absolute;
    bottom: -18rpx;
    right: 10rpx;
    border-width: 20rpx 20rpx 0;
    border-style: solid;
    border-color: #fff transparent transparent;
  }
  .disabledColor {
    color: #c5c8ce;
  }
}
.solid-bottom {
  border-bottom: 1px solid #f3f5f7;
}
.solid-right {
  border-right: 1px solid #ccc;
}
.popups .itemChild:last-child {
  border: none;
}
</style>