Skip to content

电商应用实战

电商应用是 uni-app 最常见的应用场景之一,本文将介绍如何使用 uni-app 开发一个功能完善的电商应用。

应用架构

一个典型的电商应用通常包含以下核心模块:

  • 首页:展示推荐商品、活动banner、分类入口等
  • 分类:商品分类展示
  • 商品详情:展示商品信息、规格选择、评价等
  • 购物车:管理待购商品
  • 订单:订单创建、支付、物流跟踪等
  • 个人中心:用户信息、收货地址、优惠券等
  • 搜索:商品搜索功能

技术选型

前端技术

  • uni-app:跨平台开发框架
  • Vue.js:响应式数据绑定
  • Vuex:状态管理
  • uni-ui:UI组件库

后端服务

  • 云函数:处理业务逻辑
  • 云数据库:存储商品、用户、订单等数据
  • 云存储:存储商品图片等资源
  • 支付接口:对接各平台支付功能

项目结构

├── components            // 自定义组件
│   ├── goods-card        // 商品卡片组件
│   ├── price             // 价格组件
│   └── rating            // 评分组件
├── pages                 // 页面文件夹
│   ├── index             // 首页
│   ├── category          // 分类页
│   ├── goods-detail      // 商品详情页
│   ├── cart              // 购物车
│   ├── order             // 订单相关页面
│   └── user              // 用户中心
├── static                // 静态资源
├── store                 // Vuex 状态管理
│   ├── index.js          // 组装模块并导出
│   ├── cart.js           // 购物车状态
│   └── user.js           // 用户状态
├── utils                 // 工具函数
│   ├── request.js        // 请求封装
│   └── payment.js        // 支付相关
├── App.vue               // 应用入口
├── main.js               // 主入口
├── manifest.json         // 配置文件
└── pages.json            // 页面配置

核心功能实现

1. 商品列表与筛选

商品列表是电商应用的基础功能,支持分类筛选、价格排序、销量排序等功能。

vue
<template>
  <view class="goods-list">
    <!-- 筛选栏 -->
    <view class="filter-bar">
      <view 
        class="filter-item" 
        :class="{ active: sortType === 'default' }"
        @click="changeSort('default')"
      >综合</view>
      <view 
        class="filter-item" 
        :class="{ active: sortType === 'sales' }"
        @click="changeSort('sales')"
      >销量</view>
      <view 
        class="filter-item" 
        :class="{ active: sortType === 'price' }"
        @click="changeSort('price')"
      >
        价格
        <text class="sort-icon">{{ sortOrder === 'asc' ? '↑' : '↓' }}</text>
      </view>
    </view>
    
    <!-- 商品列表 -->
    <view class="goods-container">
      <goods-card 
        v-for="item in goodsList" 
        :key="item.id" 
        :goods="item"
        @click="navigateToDetail(item.id)"
      ></goods-card>
    </view>
    
    <!-- 加载更多 -->
    <uni-load-more :status="loadMoreStatus"></uni-load-more>
  </view>
</template>

<script>
import goodsCard from '@/components/goods-card/goods-card.vue';
import uniLoadMore from '@/components/uni-load-more/uni-load-more.vue';

export default {
  components: {
    goodsCard,
    uniLoadMore
  },
  data() {
    return {
      goodsList: [],
      categoryId: '',
      keyword: '',
      page: 1,
      pageSize: 10,
      sortType: 'default', // default, sales, price
      sortOrder: 'desc', // asc, desc
      loadMoreStatus: 'more' // more, loading, noMore
    }
  },
  onLoad(options) {
    if (options.categoryId) {
      this.categoryId = options.categoryId;
    }
    if (options.keyword) {
      this.keyword = options.keyword;
    }
    
    this.loadGoodsList();
  },
  onReachBottom() {
    if (this.loadMoreStatus !== 'noMore') {
      this.page++;
      this.loadGoodsList();
    }
  },
  methods: {
    // 加载商品列表
    async loadGoodsList() {
      this.loadMoreStatus = 'loading';
      
      try {
        const params = {
          page: this.page,
          pageSize: this.pageSize,
          sortType: this.sortType,
          sortOrder: this.sortOrder,
          categoryId: this.categoryId || undefined,
          keyword: this.keyword || undefined
        };
        
        const res = await this.$api.goods.list(params);
        
        if (this.page === 1) {
          this.goodsList = res.data.list;
        } else {
          this.goodsList = [...this.goodsList, ...res.data.list];
        }
        
        this.loadMoreStatus = res.data.list.length < this.pageSize ? 'noMore' : 'more';
      } catch (e) {
        console.error(e);
        this.loadMoreStatus = 'more';
        uni.showToast({
          title: '加载失败',
          icon: 'none'
        });
      }
    },
    
    // 切换排序方式
    changeSort(type) {
      if (this.sortType === type) {
        // 同一排序类型,切换排序顺序
        if (type === 'price') {
          this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
        }
      } else {
        // 不同排序类型,设置默认排序顺序
        this.sortType = type;
        this.sortOrder = type === 'price' ? 'asc' : 'desc';
      }
      
      // 重新加载数据
      this.page = 1;
      this.loadGoodsList();
    },
    
    // 跳转到商品详情
    navigateToDetail(goodsId) {
      uni.navigateTo({
        url: `/pages/goods-detail/goods-detail?id=${goodsId}`
      });
    }
  }
}
</script>

2. 商品详情页

商品详情页是用户了解商品信息、选择规格并下单的关键页面。

vue
<template>
  <view class="goods-detail">
    <!-- 轮播图 -->
    <swiper class="swiper" indicator-dots autoplay circular>
      <swiper-item v-for="(item, index) in goods.images" :key="index">
        <image :src="item" mode="aspectFill" class="slide-image"></image>
      </swiper-item>
    </swiper>
    
    <!-- 商品信息 -->
    <view class="goods-info">
      <view class="price-box">
        <price :value="goods.price"></price>
        <text class="original-price" v-if="goods.originalPrice">¥{{goods.originalPrice}}</text>
      </view>
      <view class="title">{{goods.title}}</view>
      <view class="sub-title">{{goods.subtitle}}</view>
    </view>
    
    <!-- 规格选择 -->
    <view class="spec-section" @click="openSpecModal">
      <text class="section-title">选择</text>
      <text class="selected-text">{{selectedSpecText}}</text>
      <text class="arrow">></text>
    </view>
    
    <!-- 商品详情 -->
    <view class="detail-section">
      <view class="section-header">
        <text class="section-title">商品详情</text>
      </view>
      <rich-text :nodes="goods.detail"></rich-text>
    </view>
    
    <!-- 底部操作栏 -->
    <view class="footer">
      <view class="icon-btn">
        <text class="iconfont icon-home"></text>
        <text>首页</text>
      </view>
      <view class="icon-btn">
        <text class="iconfont icon-cart"></text>
        <text>购物车</text>
        <text class="badge" v-if="cartCount > 0">{{cartCount}}</text>
      </view>
      <button class="action-btn btn-cart" @click="addToCart">加入购物车</button>
      <button class="action-btn btn-buy" @click="buyNow">立即购买</button>
    </view>
    
    <!-- 规格选择弹窗 -->
    <uni-popup ref="specPopup" type="bottom">
      <view class="spec-popup">
        <view class="spec-header">
          <image :src="goods.thumbnail" class="goods-thumb"></image>
          <view class="spec-info">
            <price :value="selectedSkuPrice || goods.price"></price>
            <text class="stock">库存 {{selectedSkuStock || goods.stock}} 件</text>
            <text class="selected">已选:{{selectedSpecText}}</text>
          </view>
          <text class="close-btn" @click="closeSpecModal">×</text>
        </view>
        
        <scroll-view scroll-y class="spec-content">
          <view 
            class="spec-group" 
            v-for="(group, groupIndex) in goods.specs" 
            :key="groupIndex"
          >
            <text class="spec-group-title">{{group.name}}</text>
            <view class="spec-items">
              <text 
                class="spec-item" 
                :class="{ active: isSpecSelected(groupIndex, valueIndex) }"
                v-for="(value, valueIndex) in group.values" 
                :key="valueIndex"
                @click="selectSpec(groupIndex, valueIndex)"
              >{{value}}</text>
            </view>
          </view>
          
          <view class="quantity-box">
            <text class="quantity-label">数量</text>
            <uni-number-box 
              :min="1" 
              :max="selectedSkuStock || goods.stock" 
              v-model="quantity"
            ></uni-number-box>
          </view>
        </scroll-view>
        
        <view class="spec-footer">
          <button class="btn-confirm-cart" @click="confirmAddToCart">加入购物车</button>
          <button class="btn-confirm-buy" @click="confirmBuyNow">立即购买</button>
        </view>
      </view>
    </uni-popup>
  </view>
</template>

<script>
import price from '@/components/price/price.vue';
import uniPopup from '@/components/uni-popup/uni-popup.vue';
import uniNumberBox from '@/components/uni-number-box/uni-number-box.vue';
import { mapGetters, mapActions } from 'vuex';

export default {
  components: {
    price,
    uniPopup,
    uniNumberBox
  },
  data() {
    return {
      goodsId: '',
      goods: {
        id: '',
        title: '',
        subtitle: '',
        price: 0,
        originalPrice: 0,
        stock: 0,
        thumbnail: '',
        images: [],
        detail: '',
        specs: [],
        skus: []
      },
      selectedSpecs: [],
      quantity: 1,
      buyType: '' // cart, buy
    }
  },
  computed: {
    ...mapGetters('cart', ['cartCount']),
    
    // 已选规格文本
    selectedSpecText() {
      if (!this.goods.specs || this.goods.specs.length === 0) {
        return '默认';
      }
      
      if (this.selectedSpecs.length === 0) {
        return '请选择规格';
      }
      
      let text = [];
      this.goods.specs.forEach((group, index) => {
        if (this.selectedSpecs[index] !== undefined) {
          text.push(group.values[this.selectedSpecs[index]]);
        }
      });
      
      return text.join(',');
    },
    
    // 选中的SKU价格
    selectedSkuPrice() {
      const sku = this.getSelectedSku();
      return sku ? sku.price : null;
    },
    
    // 选中的SKU库存
    selectedSkuStock() {
      const sku = this.getSelectedSku();
      return sku ? sku.stock : null;
    }
  },
  onLoad(options) {
    this.goodsId = options.id;
    this.loadGoodsDetail();
  },
  methods: {
    ...mapActions('cart', ['addCart']),
    
    // 加载商品详情
    async loadGoodsDetail() {
      try {
        const res = await this.$api.goods.detail({
          id: this.goodsId
        });
        
        this.goods = res.data;
        
        // 初始化选中规格数组
        if (this.goods.specs && this.goods.specs.length > 0) {
          this.selectedSpecs = new Array(this.goods.specs.length).fill(undefined);
        }
      } catch (e) {
        console.error(e);
        uni.showToast({
          title: '加载失败',
          icon: 'none'
        });
      }
    },
    
    // 打开规格选择弹窗
    openSpecModal() {
      this.$refs.specPopup.open();
    },
    
    // 关闭规格选择弹窗
    closeSpecModal() {
      this.$refs.specPopup.close();
    },
    
    // 判断规格是否选中
    isSpecSelected(groupIndex, valueIndex) {
      return this.selectedSpecs[groupIndex] === valueIndex;
    },
    
    // 选择规格
    selectSpec(groupIndex, valueIndex) {
      this.$set(this.selectedSpecs, groupIndex, valueIndex);
    },
    
    // 获取选中的SKU
    getSelectedSku() {
      // 如果没有规格或者规格未选择完整,返回null
      if (!this.goods.specs || this.goods.specs.length === 0 || 
          this.selectedSpecs.includes(undefined)) {
        return null;
      }
      
      // 根据选中的规格查找对应的SKU
      const selectedSpecValues = this.selectedSpecs.map((valueIndex, groupIndex) => {
        return this.goods.specs[groupIndex].values[valueIndex];
      });
      
      return this.goods.skus.find(sku => {
        return JSON.stringify(sku.specs) === JSON.stringify(selectedSpecValues);
      });
    },
    
    // 加入购物车按钮点击
    addToCart() {
      this.buyType = 'cart';
      this.openSpecModal();
    },
    
    // 立即购买按钮点击
    buyNow() {
      this.buyType = 'buy';
      this.openSpecModal();
    },
    
    // 确认加入购物车
    async confirmAddToCart() {
      // 检查规格是否选择完整
      if (this.goods.specs && this.goods.specs.length > 0 && 
          this.selectedSpecs.includes(undefined)) {
        uni.showToast({
          title: '请选择完整规格',
          icon: 'none'
        });
        return;
      }
      
      // 获取选中的SKU
      const sku = this.getSelectedSku();
      
      // 构建购物车商品对象
      const cartItem = {
        goodsId: this.goods.id,
        skuId: sku ? sku.id : null,
        title: this.goods.title,
        price: sku ? sku.price : this.goods.price,
        image: this.goods.thumbnail,
        specs: this.selectedSpecText,
        quantity: this.quantity
      };
      
      // 添加到购物车
      try {
        await this.addCart(cartItem);
        uni.showToast({
          title: '已加入购物车',
          icon: 'success'
        });
        this.closeSpecModal();
      } catch (e) {
        uni.showToast({
          title: '添加失败',
          icon: 'none'
        });
      }
    },
    
    // 确认立即购买
    confirmBuyNow() {
      // 检查规格是否选择完整
      if (this.goods.specs && this.goods.specs.length > 0 && 
          this.selectedSpecs.includes(undefined)) {
        uni.showToast({
          title: '请选择完整规格',
          icon: 'none'
        });
        return;
      }
      
      // 获取选中的SKU
      const sku = this.getSelectedSku();
      
      // 构建订单商品对象
      const orderItem = {
        goodsId: this.goods.id,
        skuId: sku ? sku.id : null,
        title: this.goods.title,
        price: sku ? sku.price : this.goods.price,
        image: this.goods.thumbnail,
        specs: this.selectedSpecText,
        quantity: this.quantity
      };
      
      // 跳转到订单确认页
      uni.navigateTo({
        url: '/pages/order/confirm/confirm',
        success: (res) => {
          res.eventChannel.emit('orderData', {
            items: [orderItem],
            from: 'buyNow'
          });
        }
      });
      
      this.closeSpecModal();
    }
  }
}
</script>

3. 购物车功能

购物车是电商应用的核心功能之一,支持商品的添加、删除、修改数量等操作。

vue
<template>
  <view class="cart-page">
    <!-- 空购物车提示 -->
    <view class="empty-cart" v-if="cartList.length === 0">
      <image src="/static/images/empty-cart.png" class="empty-image"></image>
      <text class="empty-text">购物车还是空的</text>
      <button class="go-shopping-btn" @click="goShopping">去逛逛</button>
    </view>
    
    <!-- 购物车列表 -->
    <block v-else>
      <view class="cart-list">
        <view 
          class="cart-item"
          v-for="(item, index) in cartList"
          :key="item.id"
        >
          <view class="checkbox">
            <checkbox 
              :checked="item.selected" 
              @click="toggleSelect(index)"
              color="#ff6700"
            ></checkbox>
          </view>
          <image :src="item.image" class="goods-image" mode="aspectFill"></image>
          <view class="goods-info">
            <text class="goods-title">{{item.title}}</text>
            <text class="goods-specs">{{item.specs}}</text>
            <view class="price-quantity">
              <price :value="item.price"></price>
              <uni-number-box 
                :value="item.quantity" 
                :min="1"
                @change="(value) => updateQuantity(index, value)"
              ></uni-number-box>
            </view>
          </view>
          <text class="delete-btn" @click="removeCartItem(index)">×</text>
        </view>
      </view>
      
      <!-- 底部结算栏 -->
      <view class="cart-footer">
        <view class="select-all">
          <checkbox 
            :checked="isAllSelected" 
            @click="toggleSelectAll"
            color="#ff6700"
          ></checkbox>
          <text>全选</text>
        </view>
        <view class="total-info">
          <text>合计:</text>
          <price :value="totalPrice" size="32"></price>
        </view>
        <button 
          class="checkout-btn" 
          :disabled="selectedCount === 0"
          @click="checkout"
        >结算({{selectedCount}})</button>
      </view>
    </block>
  </view>
</template>

<script>
import price from '@/components/price/price.vue';
import uniNumberBox from '@/components/uni-number-box/uni-number-box.vue';
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';

export default {
  components: {
    price,
    uniNumberBox
  },
  computed: {
    ...mapState('cart', ['cartList']),
    ...mapGetters('cart', ['totalPrice', 'selectedCount', 'isAllSelected']),
  },
  methods: {
    ...mapMutations('cart', ['updateCartItem', 'removeFromCart', 'toggleSelectItem', 'toggleSelectAll']),
    ...mapActions('cart', ['checkout']),
    
    // 切换选中状态
    toggleSelect(index) {
      this.toggleSelectItem(index);
    },
    
    // 更新商品数量
    updateQuantity(index, value) {
      this.updateCartItem({
        index,
        quantity: value
      });
    },
    
    // 删除购物车商品
    removeCartItem(index) {
      uni.showModal({
        title: '提示',
        content: '确定要删除该商品吗?',
        success: (res) => {
          if (res.confirm) {
            this.removeFromCart(index);
          }
        }
      });
    },
    
    // 去购物
    goShopping() {
      uni.switchTab({
        url: '/pages/index/index'
      });
    },
    
    // 结算
    checkout() {
      if (this.selectedCount === 0) {
        return;
      }
      
      uni.navigateTo({
        url: '/pages/order/confirm/confirm',
        success: (res) => {
          res.eventChannel.emit('orderData', {
            from: 'cart'
          });
        }
      });
    }
  }
}
</script>

4. 订单确认与支付

订单确认页面用于确认订单信息、选择收货地址和支付方式等。

vue
<template>
  <view class="order-confirm">
    <!-- 收货地址 -->
    <view class="address-section" @click="chooseAddress">
      <view class="address-info" v-if="address">
        <view class="user-info">
          <text class="name">{{address.name}}</text>
          <text class="phone">{{address.phone}}</text>
        </view>
        <view class="address-detail">{{address.province}}{{address.city}}{{address.district}}{{address.detail}}</view>
      </view>
      <view class="no-address" v-else>
        <text>请选择收货地址</text>
      </view>
      <text class="iconfont icon-right"></text>
    </view>
    
    <!-- 商品列表 -->
    <view class="goods-section">
      <view class="section-title">商品信息</view>
      <view 
        class="goods-item"
        v-for="(item, index) in orderItems"
        :key="index"
      >
        <image :src="item.image" class="goods-image" mode="aspectFill"></image>
        <view class="goods-info">
          <text class="goods-title">{{item.title}}</text>
          <text class="goods-specs">{{item.specs}}</text>
        </view>
        <view class="goods-price">
          <price :value="item.price"></price>
          <text class="goods-quantity">x{{item.quantity}}</text>
        </view>
      </view>
    </view>
    
    <!-- 配送方式 -->
    <view class="delivery-section">
      <view class="section-item">
        <text class="item-label">配送方式</text>
        <view class="item-value">
          <text>快递配送</text>
        </view>
      </view>
    </view>
    
    <!-- 优惠券 -->
    <view class="coupon-section" @click="chooseCoupon">
      <view class="section-item">
        <text class="item-label">优惠券</text>
        <view class="item-value">
          <text v-if="selectedCoupon">-¥{{selectedCoupon.amount}}</text>
          <text v-else>{{availableCoupons.length > 0 ? `${availableCoupons.length}张可用` : '无可用优惠券'}}</text>
          <text class="iconfont icon-right"></text>
        </view>
      </view>
    </view>
    
    <!-- 订单金额 -->
    <view class="amount-section">
      <view class="section-item">
        <text class="item-label">商品金额</text>
        <view class="item-value">
          <price :value="goodsAmount"></price>
        </view>
      </view>
      <view class="section-item">
        <text class="item-label">运费</text>
        <view class="item-value">
          <price :value="deliveryFee"></price>
        </view>
      </view>
      <view class="section-item" v-if="selectedCoupon">
        <text class="item-label">优惠券</text>
        <view class="item-value coupon-value">
          <price :value="-selectedCoupon.amount"></price>
        </view>
      </view>
    </view>
    
    <!-- 备注 -->
    <view class="remark-section">
      <text class="remark-label">备注</text>
      <input 
        type="text" 
        v-model="remark" 
        placeholder="选填,请先和商家协商一致" 
        class="remark-input"
      />
    </view>
    
    <!-- 底部结算栏 -->
    <view class="footer">
      <view class="total-info">
        <text>合计:</text>
        <price :value="totalAmount" size="36" color="#ff6700"></price>
      </view>
      <button class="submit-btn" @click="submitOrder">提交订单</button>
    </view>
  </view>
</template>

<script>
import price from '@/components/price/price.vue';
import { mapState, mapActions } from 'vuex';

export default {
  components: {
    price
  },
  data() {
    return {
      orderItems: [],
      address: null,
      selectedCoupon: null,
      remark: '',
      orderSource: '', // cart, buyNow
      deliveryFee: 0
    }
  },
  computed: {
    ...mapState('user', ['addressList', 'availableCoupons']),
    
    // 商品总金额
    goodsAmount() {
      return this.orderItems.reduce((total, item) => {
        return total + item.price * item.quantity;
      }, 0);
    },
    
    // 订单总金额
    totalAmount() {
      let amount = this.goodsAmount + this.deliveryFee;
      if (this.selectedCoupon) {
        amount -= this.selectedCoupon.amount;
      }
      return amount > 0 ? amount : 0;
    }
  },
  methods: {
    // 计算运费
    calculateDeliveryFee() {
      // 根据商品总价和收货地址计算运费
      if (this.goodsAmount >= 99) {
        this.deliveryFee = 0; // 满99包邮
      } else {
        this.deliveryFee = 10; // 默认运费10元
      }
    },
    
    // 提交订单
    async submitOrder() {
      // 验证收货地址
      if (!this.address) {
        uni.showToast({
          title: '请选择收货地址',
          icon: 'none'
        });
        return;
      }
      
      // 构建订单数据
      const orderData = {
        addressId: this.address.id,
        items: this.orderItems,
        couponId: this.selectedCoupon ? this.selectedCoupon.id : null,
        remark: this.remark,
        deliveryFee: this.deliveryFee,
        totalAmount: this.totalAmount
      };
      
      try {
        // 创建订单
        const result = await this.createOrder(orderData);
        
        // 跳转到支付页面
        uni.navigateTo({
          url: `/pages/order/payment/payment?orderId=${result.orderId}&amount=${this.totalAmount}`
        });
      } catch (e) {
        uni.showToast({
          title: '创建订单失败',
          icon: 'none'
        });
      }
    }
  }
}
</script>

5. 个人中心

个人中心页面展示用户信息和各种功能入口,如订单管理、收货地址、优惠券等。

vue
<template>
  <view class="user-center">
    <!-- 用户信息 -->
    <view class="user-info-section">
      <view class="user-info" @click="goToUserInfo">
        <image :src="userInfo.avatar || '/static/images/default-avatar.png'" class="avatar"></image>
        <view class="info">
          <text class="nickname">{{userInfo.nickname || '未登录'}}</text>
          <text class="member-level" v-if="userInfo.id">{{userInfo.memberLevel}}</text>
        </view>
      </view>
      <view class="user-assets">
        <view class="asset-item" @click="goToWallet">
          <text class="asset-value">{{userInfo.balance || 0}}</text>
          <text class="asset-label">余额</text>
        </view>
        <view class="asset-item" @click="goToCoupon">
          <text class="asset-value">{{userInfo.couponCount || 0}}</text>
          <text class="asset-label">优惠券</text>
        </view>
        <view class="asset-item" @click="goToPoints">
          <text class="asset-value">{{userInfo.points || 0}}</text>
          <text class="asset-label">积分</text>
        </view>
      </view>
    </view>
    
    <!-- 我的订单 -->
    <view class="order-section">
      <view class="section-header" @click="goToOrderList('')">
        <text class="section-title">我的订单</text>
        <view class="more">
          <text>全部订单</text>
          <text class="iconfont icon-right"></text>
        </view>
      </view>
      <view class="order-types">
        <view class="order-type-item" @click="goToOrderList('unpaid')">
          <text class="iconfont icon-wallet"></text>
          <text>待付款</text>
          <text class="badge" v-if="orderCount.unpaid > 0">{{orderCount.unpaid}}</text>
        </view>
        <view class="order-type-item" @click="goToOrderList('unshipped')">
          <text class="iconfont icon-box"></text>
          <text>待发货</text>
          <text class="badge" v-if="orderCount.unshipped > 0">{{orderCount.unshipped}}</text>
        </view>
        <view class="order-type-item" @click="goToOrderList('shipped')">
          <text class="iconfont icon-truck"></text>
          <text>待收货</text>
          <text class="badge" v-if="orderCount.shipped > 0">{{orderCount.shipped}}</text>
        </view>
        <view class="order-type-item" @click="goToOrderList('completed')">
          <text class="iconfont icon-comment"></text>
          <text>待评价</text>
          <text class="badge" v-if="orderCount.completed > 0">{{orderCount.completed}}</text>
        </view>
        <view class="order-type-item" @click="goToAfterSale">
          <text class="iconfont icon-service"></text>
          <text>售后</text>
        </view>
      </view>
    </view>
    
    <!-- 功能列表 -->
    <view class="function-section">
      <view class="function-item" @click="goToAddress">
        <text class="iconfont icon-location"></text>
        <text>收货地址</text>
      </view>
      <view class="function-item" @click="goToFavorite">
        <text class="iconfont icon-star"></text>
        <text>我的收藏</text>
      </view>
      <view class="function-item" @click="goToFootprint">
        <text class="iconfont icon-time"></text>
        <text>浏览历史</text>
      </view>
      <view class="function-item" @click="goToSettings">
        <text class="iconfont icon-settings"></text>
        <text>设置</text>
      </view>
    </view>
    
    <!-- 客服与帮助 -->
    <view class="help-section">
      <button class="help-btn" open-type="contact">
        <text class="iconfont icon-service"></text>
        <text>联系客服</text>
      </button>
      <view class="help-btn" @click="goToHelp">
        <text class="iconfont icon-help"></text>
        <text>帮助中心</text>
      </view>
    </view>
  </view>
</template>

<script>
import { mapState, mapActions } from 'vuex';

export default {
  data() {
    return {
      orderCount: {
        unpaid: 0,
        unshipped: 0,
        shipped: 0,
        completed: 0
      }
    }
  },
  computed: {
    ...mapState('user', ['userInfo', 'isLogin'])
  },
  onShow() {
    // 检查登录状态
    if (this.isLogin) {
      // 获取用户信息
      this.getUserInfo();
      
      // 获取订单数量
      this.getOrderCount();
    }
  },
  methods: {
    ...mapActions('user', ['getUserInfo']),
    ...mapActions('order', ['getOrderCount']),
    
    // 跳转到用户信息页
    goToUserInfo() {
      if (!this.isLogin) {
        uni.navigateTo({
          url: '/pages/auth/login/login'
        });
        return;
      }
      
      uni.navigateTo({
        url: '/pages/user/info/info'
      });
    },
    
    // 跳转到订单列表
    goToOrderList(status) {
      if (!this.isLogin) {
        uni.navigateTo({
          url: '/pages/auth/login/login'
        });
        return;
      }
      
      uni.navigateTo({
        url: `/pages/order/list/list?status=${status}`
      });
    },
    
    // 跳转到售后页面
    goToAfterSale() {
      if (!this.isLogin) {
        uni.navigateTo({
          url: '/pages/auth/login/login'
        });
        return;
      }
      
      uni.navigateTo({
        url: '/pages/order/after-sale/after-sale'
      });
    },
    
    // 跳转到钱包页面
    goToWallet() {
      if (!this.isLogin) {
        uni.navigateTo({
          url: '/pages/auth/login/login'
        });
        return;
      }
      
      uni.navigateTo({
        url: '/pages/user/wallet/wallet'
      });
    },
    
    // 跳转到优惠券页面
    goToCoupon() {
      if (!this.isLogin) {
        uni.navigateTo({
          url: '/pages/auth/login/login'
        });
        return;
      }
      
      uni.navigateTo({
        url: '/pages/user/coupon/coupon'
      });
    },
    
    // 跳转到积分页面
    goToPoints() {
      if (!this.isLogin) {
        uni.navigateTo({
          url: '/pages/auth/login/login'
        });
        return;
      }
      
      uni.navigateTo({
        url: '/pages/user/points/points'
      });
    },
    
    // 跳转到收货地址页面
    goToAddress() {
      if (!this.isLogin) {
        uni.navigateTo({
          url: '/pages/auth/login/login'
        });
        return;
      }
      
      uni.navigateTo({
        url: '/pages/user/address/list'
      });
    },
    
    // 跳转到收藏页面
    goToFavorite() {
      if (!this.isLogin) {
        uni.navigateTo({
          url: '/pages/auth/login/login'
        });
        return;
      }
      
      uni.navigateTo({
        url: '/pages/user/favorite/favorite'
      });
    },
    
    // 跳转到浏览历史页面
    goToFootprint() {
      if (!this.isLogin) {
        uni.navigateTo({
          url: '/pages/auth/login/login'
        });
        return;
      }
      
      uni.navigateTo({
        url: '/pages/user/footprint/footprint'
      });
    },
    
    // 跳转到设置页面
    goToSettings() {
      uni.navigateTo({
        url: '/pages/user/settings/settings'
      });
    },
    
    // 跳转到帮助中心
    goToHelp() {
      uni.navigateTo({
        url: '/pages/help/help'
      });
    }
  }
}
</script>

总结

通过 uni-app 开发电商应用,我们可以实现跨平台的商城功能,包括商品展示、购物车、订单管理等核心功能。本文介绍了电商应用的基本架构和关键功能实现,开发者可以在此基础上进行扩展和优化,打造功能完善的电商平台。

主要优势包括:

  1. 跨平台兼容:一套代码可以同时运行在多个平台,节省开发成本
  2. 组件化开发:利用 Vue.js 的组件化特性,提高代码复用性和可维护性
  3. 状态管理:使用 Vuex 进行状态管理,方便处理复杂的数据流
  4. 云服务集成:可以方便地集成云函数、云数据库等服务,快速实现后端功能

在实际开发中,还需要考虑性能优化、安全性、用户体验等方面,不断完善和迭代,以满足用户需求和业务发展。

一次开发,多端部署 - 让跨平台开发更简单