微信小程序电商实战 03:NutUI 配置与电商常用组件

微信小程序电商实战 01 技术选型 | 02 脚手架搭建 | 03 NutUI 配置 ← 本文 | 04 云开发环境 | 05 商品模块 | 06 购物车与订单 | 07 微信支付 | 08 用户中心 | 09 性能优化 | 10 上线部署


# NutUI 电商组件实战 ## 安装与配置 ### 安装 @nutui/nutui-taro ### babel-plugin-import 按需加载 ### 主题定制(CSS变量) ### 全局注册 vs 按需引入 ## 电商必用组件(6类) ### 展示类 - Image 懒加载图片 - Price 价格格式化 - Badge 角标 ### 商品类 - Sku SKU选择器 - InfiniteLoading 无限滚动 ### 购物类 - InputNumber 商品数量 - Checkbox 多选(购物车) ### 反馈类 - Toast 提示 - Dialog 确认框 - Loading 加载 ### 表单类 - Form 订单填写 - Address 地址选择 ### 导航类 - Tabs 商品分类 - Swipe 轮播图 ## 主题定制 ### 电商红色主题 ### CSS 变量覆盖

一、安装

pnpm add @nutui/nutui-taro @nutui/icons-vue-taro
pnpm add -D @babel/core babel-plugin-import

二、按需加载配置

NutUI 组件库体积较大,必须开启按需加载,否则打包体积超标。

修改 babel.config.js

// babel.config.js
module.exports = {
  presets: [
    ['taro/babel', { framework: 'vue', ts: true }],
  ],
  plugins: [
    [
      'import',
      {
        libraryName: '@nutui/nutui-taro',
        libraryDirectory: 'dist/packages',
        style: true,
        camel2DashComponentName: false,
      },
      'nutui-taro',
    ],
  ],
}

配置后,只有实际引用的组件代码才会打进包里。


三、全局注册常用组件

app.ts 中注册高频组件,避免每个页面重复引入:

// src/app.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import {
  Button, Toast, Dialog, Loading,
  Image as NutImage, Price, Badge,
} from '@nutui/nutui-taro'
import App from './app.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.use(Button)
app.use(Toast)
app.use(Dialog)
app.use(Loading)
app.use(NutImage)
app.use(Price)
app.use(Badge)

export default app

电商页面特有组件(SKU 选择器、地址选择器等)在页面内按需引入,避免主包体积过大。


四、主题定制

NutUI 使用 CSS 变量实现主题,在 app.scss 中覆盖:

// src/app.scss
// 电商场景主色:红色系
:root {
  --nutui-brand-color: #E31D1A;
  --nutui-brand-color-start: #E31D1A;
  --nutui-brand-color-end: #C01A17;

  // 价格色
  --nutui-price-symbol-color: #E31D1A;
  --nutui-price-integer-color: #E31D1A;

  // 按钮
  --nutui-button-primary-background-color: #E31D1A;
  --nutui-button-primary-border-color: #E31D1A;

  // 徽标
  --nutui-badge-background-color: #E31D1A;
}

五、电商核心组件用法

1. Price — 价格展示

电商最常用,自动处理整数、小数、货币符号:

<template>
  <!-- 输出:¥ 199.00 -->
  <nut-price :price="199" :decimal-digits="2" symbol="¥" />

  <!-- 划线原价 -->
  <nut-price :price="299" size="small" :strikethrough="true" />
</template>

<script setup lang="ts">
import { Price as NutPrice } from '@nutui/nutui-taro'
</script>

2. Image — 懒加载商品图

<template>
  <nut-image
    :src="product.coverImage"
    width="100%"
    height="200px"
    fit="cover"
    lazy-load
    radius="8px"
  />
</template>

3. Badge — 购物车角标

<template>
  <!-- tabBar 购物车图标上的数量角标 -->
  <nut-badge :value="cartStore.totalCount" :max="99">
    <nut-icon name="cart" />
  </nut-badge>
</template>

<script setup lang="ts">
import { useCartStore } from '@/stores/cart'
const cartStore = useCartStore()
</script>

4. InputNumber — 商品数量调节

<template>
  <nut-input-number
    v-model="quantity"
    :min="1"
    :max="product.stock"
    :disabled="product.stock === 0"
    @change="handleQuantityChange"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { InputNumber as NutInputNumber } from '@nutui/nutui-taro'

const quantity = ref(1)
const handleQuantityChange = (val: number) => {
  console.log('数量变化:', val)
}
</script>

5. Sku — SKU 选择器

SKU 选择器是电商最复杂的组件,NutUI 内置了完整的规格选择逻辑:

<template>
  <nut-sku
    v-model:visible="skuVisible"
    :sku="skuData.tree"
    :goods="skuData.goods"
    :goods-id="productId"
    :hide-one-sku="false"
    :btn-extra-text="product.stock === 0 ? '已售罄' : ''"
    @select-sku="onSelectSku"
    @add-cart="onAddCart"
    @buy-clicked="onBuyNow"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Sku as NutSku } from '@nutui/nutui-taro'

const skuVisible = ref(false)

// SKU 数据结构示例
const skuData = {
  tree: [
    {
      k: '颜色',
      k_s: 's1',
      v: [
        { id: 1, name: '黑色', imgUrl: '' },
        { id: 2, name: '白色', imgUrl: '' },
      ],
    },
    {
      k: '尺寸',
      k_s: 's2',
      v: [
        { id: 10, name: 'S' },
        { id: 11, name: 'M' },
        { id: 12, name: 'L' },
      ],
    },
  ],
  goods: [
    { id: '101', s1: 1, s2: 10, price: '199.00', stock_num: 50 },
    { id: '102', s1: 1, s2: 11, price: '199.00', stock_num: 30 },
    { id: '103', s1: 2, s2: 10, price: '209.00', stock_num: 0 },
  ],
}

const onSelectSku = (skuInfo: any) => {
  console.log('选中规格:', skuInfo)
}

const onAddCart = (skuInfo: any) => {
  // 加入购物车逻辑(下一篇详细实现)
}

const onBuyNow = (skuInfo: any) => {
  // 立即购买逻辑
}
</script>

6. Toast 和 Dialog — 操作反馈

// 在 composable 或页面中使用
import { showToast, showDialog } from '@nutui/nutui-taro'

// 成功提示
showToast.success('已加入购物车')

// 失败提示
showToast.fail('库存不足')

// 确认删除
showDialog({
  title: '删除商品',
  content: '确认从购物车移除该商品?',
  onOk: () => {
    cartStore.removeItem(skuId)
  },
})

六、商品列表页完整示例

把上面的组件组合成一个商品卡片:

<!-- src/components/ProductCard.vue -->
<template>
  <view class="product-card" @tap="goDetail">
    <nut-image
      :src="product.coverImage"
      width="100%"
      height="180px"
      fit="cover"
      lazy-load
    />
    <view class="product-info">
      <text class="product-name">{{ product.name }}</text>
      <view class="product-price-row">
        <nut-price :price="product.price" />
        <nut-price :price="product.originalPrice" size="small" :strikethrough="true" />
      </view>
      <view class="product-meta">
        <text class="sold-count">已售 {{ product.soldCount }}</text>
        <nut-button size="small" type="primary" @click.stop="openSku">加入购物车</nut-button>
      </view>
    </view>
  </view>
</template>

<script setup lang="ts">
import Taro from '@tarojs/taro'
import { Price as NutPrice, Button as NutButton, Image as NutImage } from '@nutui/nutui-taro'

interface Product {
  id: string
  name: string
  price: number
  originalPrice: number
  coverImage: string
  soldCount: number
}

const props = defineProps<{ product: Product }>()
const emit = defineEmits<{ openSku: [productId: string] }>()

const goDetail = () => {
  Taro.navigateTo({ url: `/pages/product/index?id=${props.product.id}` })
}

const openSku = () => {
  emit('openSku', props.product.id)
}
</script>

<style lang="scss">
.product-card {
  background: #fff;
  border-radius: 8px;
  overflow: hidden;
  margin-bottom: 16px;
}
.product-info {
  padding: 8px 12px 12px;
}
.product-name {
  font-size: 14px;
  line-height: 1.4;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}
.product-price-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 6px 0;
}
.product-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.sold-count {
  font-size: 12px;
  color: #999;
}
</style>

微信小程序电商实战 01 技术选型 | 02 脚手架搭建 | 03 NutUI 配置 ← 本文 | 04 云开发环境 | 05 商品模块 | 06 购物车与订单 | 07 微信支付 | 08 用户中心 | 09 性能优化 | 10 上线部署

实操清单

  • pnpm add @nutui/nutui-taro @nutui/icons-vue-taro
  • pnpm add -D @babel/core babel-plugin-import 并配置 babel.config.js 开启按需加载
  • app.ts 全局注册 Button、Toast、Dialog、Loading、Image、Price、Badge
  • app.scss 覆盖 CSS 变量,设置电商主色(--nutui-brand-color: #E31D1A
  • 在商品列表页使用 <nut-image lazy-load> 验证图片懒加载效果
  • <nut-price> 展示商品价格,验证格式化输出正确
  • 在购物车 tabBar 图标上添加 <nut-badge> 角标,与 useCartStore.totalCount 联动
  • 创建 src/components/ProductCard.vue,集成 Price + Image + Badge
  • 在测试页面引入 <nut-sku>,填入 mock 数据,验证 SKU 选择弹窗正常运作
  • 调用 showToast.success('加入成功') 验证反馈组件可用