当前位置: 首页 > article >正文

【组件分享】商品列表组件-最佳实践

商品列表组件

商品列表组件用于展示商品信息列表,支持多种布局方式和自定义配置。
在这里插入图片描述

基础用法

<template>
  <ProGoodsList :goods-list="goodsList" :layout="layout" @item-click="handleItemClick" />
</template>

<script setup>
import { ref } from 'vue';

const layout = ref('grid'); // 'grid' | 'list'
const goodsList = ref([
  {
    id: 1,
    name: '商品名称',
    price: 99.99,
    image: 'https://example.com/image.jpg',
    description: '商品描述',
  },
]);

const handleItemClick = (item) => {
  console.log('点击商品:', item);
};
</script>

API

Props

参数说明类型默认值
goodsList商品列表数据Array[]
layout布局方式String‘grid’
showPrice是否显示价格Booleantrue
showDescription是否显示描述Booleantrue

Events

事件名称说明回调参数
item-click点击商品项时触发(item: Object)
load-more加载更多时触发-

Slots

名称说明
item自定义商品项内容
empty自定义空状态内容

主题定制

组件支持通过 CSS 变量进行主题定制:

.pro-goods-list {
  --goods-item-bg: #ffffff;
  --goods-item-padding: 16px;
  --goods-item-radius: 8px;
  --goods-item-gap: 16px;
  --goods-price-color: #ff6b6b;
}

布局示例

网格布局

<template>
  <ProGoodsList :goods-list="goodsList" layout="grid" />
</template>

列表布局

<template>
  <ProGoodsList :goods-list="goodsList" layout="list" />
</template>

商品列表组件

<template>
  <view class="goods">
    <view v-if="list.length > 0" class="scroll-wrapper">
      <scroll-view
        :scroll-top="scrollTop"
        :scroll-y="scrollY"
        :class="scrollY && 'scroll-Y'"
        @scrolltolower="onScrolltolower"
      >
        <view class="goods-card-wrapper">
          <view
            v-for="(item, index) in list"
            :key="index"
            @click.stop="itemClick(item)"
            class="card-item"
          >
            <view class="pro-good-card">
              <view class="img-wrapper-A" :class="cardType === 'B' && 'img-wrapper-B'">
                <image class="pro-good-img" mode="widthFix" :src="item.picUrl" alt="" />
              </view>
              <view class="pro-good-info">
                <view class="first-title">
                  {{ item.name }}
                </view>
                <view
                  v-if="item.goodsDes"
                  class="sec-title-A"
                  :class="cardType === 'B' && 'sec-title-A'"
                >
                  {{ item.goodsDes }}
                </view>
                <view class="pro-good-price-A" :class="cardType === 'B' && 'pro-good-price-B'">
                  <view>
                    <text v-if="priceInfo.unit" class="price-unit">{{ priceInfo.unit }}</text>
                    <text class="retail-price">
                      {{ item.countPrice }}
                    </text>
                    <text v-if="priceInfo.des" class="price-des">{{ priceInfo.des }}</text>
                  </view>
                  <view v-if="item.counterPrice" class="counter-price">
                    ¥{{ item.counterPrice }}
                  </view>
                </view>
              </view>
            </view>
          </view>
        </view>
      </scroll-view>
    </view>
    <view v-else>
      <image
        class="empty-img"
        src="https://static.wxb.com.cn/frontEnd/images/benz-mp/goods-empty.png"
      ></image>
      <view class="empty-text">暂无商品</view>
    </view>
  </view>
</template>
<script setup>
  import { ref, computed } from "vue";

  const props = defineProps({
    // 列表数据
    list: {
      type: Array,
      required: true,
      default: () => [],
    },
    // 卡片类型:A/B
    cardType: {
      type: String,
      default: "A",
    },
    // 卡片样式
    goodsStyle: {
      type: Object,
      default: () => {
        return {};
      },
    },
    // 价格相关的信息
    priceInfo: {
      type: Object,
      default: () => {
        return {
          unit: "¥",
          des: "起",
        };
      },
    },
    // 是否开启上拉加载
    scrollY: {
      type: Boolean,
      default: false,
    },
    //  滚动高度
    scrollHeight: {
      type: Number,
      default: 0,
    },
  });
  const scrollHeight = props.scrollHeight;
  const _goodsStyle = computed(() => {
    const options = {
      borderRadius: "16rpx", // 卡片圆角
      bgColor: props.cardType === "A" ? "#fff" : "rgba(255, 255, 255, 0.74)", // 卡片背景颜色
      //一级标题样式
      firstTitle: {
        fontSize: "28rpx",
        color: "#333",
        fontFamily: "PingFang SC, sans-serif",
      },
      //二级标题样式
      secondTitle: {
        fontSize: "22rpx",
        color: "#666",
        fontFamily: "PingFang SC, sans-serif",
      },
      priceColor: "#FF4D3B", // 价格字体颜色
      columnGap: "24rpx", // 列间距
      rowGap: "24rpx", // 行间距
    };
    return Object.assign(options, props.goodsStyle);
  });
  // 卡片圆角
  const borderRadius = _goodsStyle.value.borderRadius;
  // 一级标题样式
  const { fontSize: ffs, color: fc, fontFamily: ffm } = _goodsStyle.value.firstTitle;
  // 二级标题样式
  const { fontSize: sfs, color: sc, fontFamily: sfm } = _goodsStyle.value.secondTitle;
  // 价格颜色
  const priceColor = _goodsStyle.value.priceColor;
  // 卡片背景色
  const bgColor = _goodsStyle.value.bgColor;
  // 列间距
  const columnGap = _goodsStyle.value.columnGap;
  // 行间距
  const rowGap = _goodsStyle.value.rowGap;

  // 点击事件
  const emits = defineEmits(["itemClick", "scroll"]);
  const itemClick = (item) => {
    emits("itemClick", item.id);
  };

  const onScrolltolower = () => {
    console.log("上拉加载");
    emits("scroll");
  };
</script>
<style scoped lang="scss">
  @import '../base.scss';
  // 用于数字的特殊字体
  @font-face {
    font-family: "TCloudNumberRegular";
    src: url("./fonts/TCloudNumber-Regular.ttf") format("truetype"),
      url("./fonts/TCloudNumber-Regular.ttf") format("woff2");
  }
  .goods {
    text-align: center;
    height: v-bind(scrollHeight);
    .scroll-wrapper {
      height: 100%;
      .scroll-Y {
        height: 100%;
      }
      .goods-card-wrapper {
        column-gap: v-bind(columnGap);
        column-count: 2;
        .card-item {
          margin-bottom: 24rpx;
          break-inside: avoid; //避免子元素内容被截取
          .pro-good-card {
            width: 100%;
            background: v-bind(bgColor);
            border-radius: v-bind(borderRadius);
            overflow: hidden;
            .img-wrapper-A {
              padding: 12rpx 12rpx 0 12rpx;
              .pro-good-img {
                display: block;
                width: 100%;
                border-radius: 12rpx;
              }
            }
            .img-wrapper-B {
              padding: 0;
              .pro-good-img {
                display: block;
                width: 100%;
                border-radius: 0;
              }
            }

            .pro-good-info {
              padding: 0rpx 20rpx 28rpx 20rpx;
            }
            .first-title {
              font-size: v-bind(ffs);
              color: v-bind(fc);
              font-family: v-bind(ffm);
              font-weight: 500;
              line-height: 36rpx;
              overflow: hidden;
              text-overflow: ellipsis;
              display: -webkit-box;
              -webkit-box-orient: vertical;
              -webkit-line-clamp: 2;
              margin-top: 20rpx;
              text-align: left;
            }
            .first-title-B {
              margin-top: 10rpx;
            }
            .sec-title-A {
              font-size: v-bind(sfs);
              color: v-bind(sc);
              font-family: v-bind(sfm);
              line-height: 32rpx;
              margin-top: 8rpx;
              overflow: hidden;
              text-overflow: ellipsis;
              display: -webkit-box;
              -webkit-box-orient: vertical;
              -webkit-line-clamp: 1;
              text-align: left;
            }
            .sec-title-B {
              margin-top: 4rpx;
            }
            .pro-good-price-A {
              display: flex;
              align-items: baseline;
              margin-top: 24rpx;
              .price-unit {
                font-size: 20rpx;
                font-family: "TCloudNumberRegular";
                font-weight: 400;
                color: v-bind(priceColor);
                line-height: 24rpx;
              }
              .price-des {
                font-size: 20rpx;
                font-family: PingFang SC, sans-serif;
                font-weight: 500;
                color: v-bind(priceColor);
                line-height: 28rpx;
                margin-left: 4rpx;
              }
              .retail-price {
                font-size: 40rpx;
                color: v-bind(priceColor);
                line-height: 44rpx;
                font-family: "TCloudNumberRegular";
                vertical-align: middle;
              }
              .counter-price {
                font-size: 24rpx;
                text-decoration: line-through;
                margin-left: 12rpx;
                color: #cecece;
                font-family: "TCloudNumberRegular";
              }
            }
            .pro-good-price-B {
              margin-top: 20rpx;
            }
          }
        }
      }
    }
  }
</style>


http://www.kler.cn/a/514827.html

相关文章:

  • 【AI编辑器】字节跳动推出AI IDE——Trae,专为中文开发者深度定制
  • Arweave的出块原理
  • 基于微信小程序高校订餐系统的设计与开发ssm+论文源码调试讲解
  • 初始SpringBoot:详解特性和结构
  • 【Vim Masterclass 笔记22】S09L40 + L41:同步练习11:Vim 的配置与 vimrc 文件的相关操作(含点评课内容)
  • 为什么你的 Qt 应用程序会出现 xcb 插件错误
  • 【子矩阵——优先队列】
  • Leecode刷题C语言之从栈中取出K个硬币的最大面积和
  • node.js 07.npm下包慢的问题与nrm的使用
  • Java 设计模式一
  • 聚类OTU vs 降噪识别生物序列——谁将主宰扩增子领域未来
  • CSDN 博客之星 2024:默语的技术进阶与社区耕耘之旅
  • Markdown Viewer 浏览器, vscode
  • 如何为64位LabVIEW配置正确的驱动程序
  • 基于STM32F103驱动AD7606串行采集数据信号
  • C++之初识模版
  • 安卓APP如何适配不同的手机分辨率
  • 【动态规划】--- 斐波那契数模型
  • 短剧系统开发功能需求/APP开发/源码指南
  • 计算在不规则形状内不同结构的占比
  • winfrom项目,引用EPPlus.dll实现将DataTable 中的数据保存到Excel文件
  • 鸿蒙开发入门之Hello World
  • 炸场硅谷,大模型“蒸汽机”迎来“瓦特时刻”
  • 论文速读|Multi-Modal Disordered Representation Learning Network for TBPS.AAAI24
  • 错误记录(二)virtualbox连共享文件夹
  • 09_异步加载_单例模式_常量类配置_不可销毁