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 Math.max(amount, 0);
    }
  },
  onLoad() {
    const eventChannel = this.getOpenerEventChannel();
    eventChannel.on('orderData', (data) => {
      this.orderSource = data.from;
      if (data.from === 'cart') {
        this.loadCartItems();
      } else if (data.from === 'buyNow') {
        this.orderItems = data.items;
      }
    });
    
    this.loadDefaultAddress();
    this.loadAvailableCoupons();
  },
  methods: {
    ...mapActions('user', ['loadAddressList', 'loadCoupons']),
    
    // 載入購物車商品
    loadCartItems() {
      // 從購物車載入選中的商品
      const selectedItems = this.$store.getters['cart/selectedItems'];
      this.orderItems = selectedItems;
    },
    
    // 載入預設地址
    async loadDefaultAddress() {
      await this.loadAddressList();
      this.address = this.addressList.find(item => item.isDefault) || this.addressList[0];
    },
    
    // 載入可用優惠券
    async loadAvailableCoupons() {
      await this.loadCoupons();
    },
    
    // 選擇地址
    chooseAddress() {
      uni.navigateTo({
        url: '/pages/user/address/list/list?from=order'
      });
    },
    
    // 選擇優惠券
    chooseCoupon() {
      if (this.availableCoupons.length === 0) {
        return;
      }
      
      uni.navigateTo({
        url: '/pages/user/coupon/list/list?from=order'
      });
    },
    
    // 提交訂單
    async submitOrder() {
      if (!this.address) {
        uni.showToast({
          title: '請選擇收貨地址',
          icon: 'none'
        });
        return;
      }
      
      if (this.orderItems.length === 0) {
        uni.showToast({
          title: '訂單商品不能為空',
          icon: 'none'
        });
        return;
      }
      
      try {
        const orderData = {
          items: this.orderItems,
          address: this.address,
          coupon: this.selectedCoupon,
          remark: this.remark,
          totalAmount: this.totalAmount
        };
        
        const res = await this.$api.order.create(orderData);
        
        // 跳轉到付款頁面
        uni.redirectTo({
          url: `/pages/order/payment/payment?orderId=${res.data.orderId}`
        });
        
      } catch (e) {
        console.error(e);
        uni.showToast({
          title: '訂單提交失敗',
          icon: 'none'
        });
      }
    }
  }
}
</script>

最佳實踐

1. 效能最佳化

  • 圖片最佳化:使用適當的圖片格式和尺寸,啟用圖片懶載入
  • 資料分頁:商品列表採用分頁載入,避免一次載入過多資料
  • 快取策略:合理使用本地儲存快取常用資料
  • 元件最佳化:使用虛擬列表處理大量資料展示

2. 使用者體驗

  • 載入狀態:為所有非同步操作提供載入狀態提示
  • 錯誤處理:友善的錯誤提示和重試機制
  • 離線支援:關鍵功能支援離線使用
  • 響應式設計:適配不同螢幕尺寸和解析度

3. 安全性

  • 資料驗證:前後端雙重資料驗證
  • 付款安全:使用官方付款介面,避免敏感資訊洩露
  • 使用者認證:完善的登入驗證和權限控制
  • 資料加密:敏感資料傳輸加密

4. 跨平台適配

  • 樣式適配:使用條件編譯處理平台差異
  • 功能適配:根據平台特性調整功能實現
  • 測試覆蓋:在各目標平台進行充分測試

總結

透過本文的介紹,我們了解了如何使用 uni-app 開發一個功能完善的電商應用。從架構設計到核心功能實現,再到最佳實踐,每個環節都需要仔細考慮。

電商應用的成功不僅在於功能的完整性,更在於使用者體驗的優化和效能的提升。希望本文能為你的 uni-app 電商專案開發提供有價值的參考。

相關資源

一次開發,多端部署 - 讓跨平台開發更簡單