阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 开发一个跨平台电商应用

开发一个跨平台电商应用

作者:陈川 阅读数:35433人阅读 分类: uni-app

跨平台电商应用开发背景

电商应用开发需要考虑多端适配问题,传统开发方式需要为iOS、Android、Web等平台分别开发,成本高且维护困难。uni-app作为基于Vue.js的跨平台框架,可以一次开发同时发布到多个平台,大幅提升开发效率。一个典型的电商应用通常包含商品展示、购物车、订单管理、支付等核心功能模块。

项目初始化与配置

使用HBuilderX创建uni-app项目时,选择默认模板即可快速搭建基础结构。项目目录中pages存放页面文件,static存放静态资源,App.vue是应用入口文件。manifest.json文件配置应用名称、图标等基本信息。

// manifest.json示例配置
{
  "name": "电商商城",
  "appid": "",
  "description": "",
  "versionName": "1.0.0",
  "versionCode": "100",
  "transformPx": false,
  "app-plus": {
    "splashscreen": {
      "alwaysShowBeforeRender": true,
      "autoclose": true,
      "delay": 0
    }
  }
}

核心页面开发

首页开发

首页通常包含轮播图、商品分类、推荐商品等模块。使用uni-app的swiper组件实现轮播效果:

<template>
  <view>
    <swiper :indicator-dots="true" :autoplay="true" :interval="3000">
      <swiper-item v-for="(item,index) in banners" :key="index">
        <image :src="item.image" mode="aspectFill"></image>
      </swiper-item>
    </swiper>
    
    <view class="category-list">
      <view v-for="(item,index) in categories" :key="index" 
            @click="navToCategory(item.id)">
        <image :src="item.icon"></image>
        <text>{{item.name}}</text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      banners: [
        {image: '/static/banner1.jpg', link: ''},
        {image: '/static/banner2.jpg', link: ''}
      ],
      categories: [
        {id: 1, name: '手机', icon: '/static/phone.png'},
        {id: 2, name: '电脑', icon: '/static/computer.png'}
      ]
    }
  },
  methods: {
    navToCategory(id) {
      uni.navigateTo({
        url: `/pages/category/list?id=${id}`
      })
    }
  }
}
</script>

商品详情页

商品详情页需要展示商品图片、价格、规格选择、评价等信息。使用uni-popup组件实现规格选择弹窗:

<template>
  <view class="detail-container">
    <swiper class="image-swiper" :indicator-dots="true">
      <swiper-item v-for="(img,index) in goods.images" :key="index">
        <image :src="img" mode="aspectFit"></image>
      </swiper-item>
    </swiper>
    
    <view class="info-section">
      <view class="price">¥{{goods.price}}</view>
      <view class="title">{{goods.title}}</view>
      <view class="sales">已售{{goods.sales}}件</view>
    </view>
    
    <view class="spec-section" @click="showSpecPopup">
      <text>选择规格</text>
      <uni-icons type="arrowright"></uni-icons>
    </view>
    
    <uni-popup ref="specPopup" type="bottom">
      <view class="spec-popup">
        <!-- 规格选择内容 -->
      </view>
    </uni-popup>
  </view>
</template>

购物车功能实现

购物车需要管理商品选择、数量修改、全选等交互。使用Vuex进行状态管理:

// store/cart.js
const state = {
  cartItems: []
}

const mutations = {
  ADD_TO_CART(state, goods) {
    const existingItem = state.cartItems.find(item => item.id === goods.id)
    if(existingItem) {
      existingItem.quantity += 1
    } else {
      state.cartItems.push({
        ...goods,
        quantity: 1,
        selected: true
      })
    }
  },
  UPDATE_QUANTITY(state, {id, quantity}) {
    const item = state.cartItems.find(item => item.id === id)
    if(item) item.quantity = quantity
  }
}

export default {
  namespaced: true,
  state,
  mutations
}

购物车页面实现:

<template>
  <view>
    <view class="cart-list">
      <checkbox-group @change="toggleSelect">
        <view v-for="item in cartItems" :key="item.id" class="cart-item">
          <checkbox :value="item.id" :checked="item.selected"></checkbox>
          <image :src="item.image"></image>
          <view class="info">
            <text>{{item.title}}</text>
            <view class="price">¥{{item.price}}</view>
            <view class="quantity">
              <text @click="decrease(item.id)">-</text>
              <input type="number" v-model="item.quantity" @blur="updateQuantity(item)">
              <text @click="increase(item.id)">+</text>
            </view>
          </view>
        </view>
      </checkbox-group>
    </view>
    
    <view class="cart-footer">
      <checkbox :checked="allSelected" @click="toggleAll">全选</checkbox>
      <view class="total">
        合计:¥{{totalPrice}}
      </view>
      <button @click="checkout">结算({{selectedCount}})</button>
    </view>
  </view>
</template>

订单与支付流程

订单确认页展示收货地址、商品清单、优惠信息等:

<template>
  <view>
    <view class="address-section" @click="selectAddress">
      <view v-if="selectedAddress">
        <text>{{selectedAddress.name}} {{selectedAddress.phone}}</text>
        <text>{{selectedAddress.fullAddress}}</text>
      </view>
      <view v-else>
        <text>请选择收货地址</text>
      </view>
    </view>
    
    <view class="order-goods">
      <view v-for="item in selectedItems" :key="item.id">
        <image :src="item.image"></image>
        <view>
          <text>{{item.title}}</text>
          <text>¥{{item.price}} x {{item.quantity}}</text>
        </view>
      </view>
    </view>
    
    <view class="order-submit">
      <text>实付:¥{{totalAmount}}</text>
      <button @click="createOrder">提交订单</button>
    </view>
  </view>
</template>

支付流程处理:

methods: {
  async createOrder() {
    const orderData = {
      addressId: this.selectedAddress.id,
      goods: this.selectedItems.map(item => ({
        id: item.id,
        quantity: item.quantity
      }))
    }
    
    try {
      const res = await uni.request({
        url: '/api/orders',
        method: 'POST',
        data: orderData
      })
      
      this.payOrder(res.data.orderId)
    } catch (error) {
      uni.showToast({ title: '创建订单失败', icon: 'none' })
    }
  },
  
  payOrder(orderId) {
    uni.requestPayment({
      provider: 'wxpay',
      orderInfo: { orderId },
      success: () => {
        uni.redirectTo({
          url: `/pages/order/result?orderId=${orderId}`
        })
      },
      fail: (err) => {
        console.error('支付失败', err)
      }
    })
  }
}

多平台适配处理

不同平台需要处理样式和功能差异:

/* 条件编译处理平台差异 */
/* #ifdef H5 */
.header {
  height: 44px;
}
/* #endif */

/* #ifdef MP-WEIXIN */
.header {
  height: 48px;
  padding-top: 20px;
}
/* #endif */

/* #ifdef APP-PLUS */
.header {
  height: 44px;
  padding-top: 20px;
}
/* #endif */

平台特定API调用示例:

// 微信小程序获取用户信息
// #ifdef MP-WEIXIN
uni.getUserProfile({
  desc: '用于完善会员资料',
  success: (res) => {
    this.userInfo = res.userInfo
  }
})
// #endif

// APP端调用原生功能
// #ifdef APP-PLUS
plus.share.sendWithSystem({
  content: '分享内容',
  href: 'https://example.com'
})
// #endif

性能优化策略

  1. 图片懒加载:
<image lazy-load :src="item.image" mode="aspectFill"></image>
  1. 分页加载商品列表:
async loadMore() {
  if(this.loading || this.finished) return
  
  this.loading = true
  const res = await this.$http.get('/api/goods', {
    page: this.page + 1,
    size: 10
  })
  
  this.list = [...this.list, ...res.data.list]
  this.page = res.data.page
  this.finished = res.data.finished
  this.loading = false
}
  1. 使用分包加载:
// pages.json
{
  "subPackages": [
    {
      "root": "subpackageA",
      "pages": [
        {
          "path": "goods/list",
          "style": {}
        }
      ]
    }
  ]
}

数据缓存策略

使用uni-app的存储API缓存数据:

// 获取缓存数据
async getCachedData() {
  try {
    const cache = await uni.getStorage({ key: 'goodsCache' })
    if(cache && Date.now() - cache.timestamp < 3600000) {
      return cache.data
    }
    return null
  } catch (e) {
    return null
  }
}

// 更新缓存
async updateCache(data) {
  await uni.setStorage({
    key: 'goodsCache',
    data: {
      data,
      timestamp: Date.now()
    }
  })
}

用户反馈与评价系统

商品评价组件实现:

<template>
  <view class="review-section">
    <view class="review-header">
      <text>商品评价({{reviews.length}})</text>
      <text @click="showAllReviews">查看全部</text>
    </view>
    
    <view class="review-list">
      <view v-for="(item,index) in reviews.slice(0,2)" :key="index">
        <view class="user-info">
          <image :src="item.avatar"></image>
          <text>{{item.nickname}}</text>
        </view>
        <view class="rating">
          <uni-rate :value="item.rating" readonly></uni-rate>
          <text>{{item.date}}</text>
        </view>
        <view class="content">{{item.content}}</view>
      </view>
    </view>
  </view>
</template>

实时通讯功能

集成WebSocket实现客服功能:

let socketTask = null

export default {
  data() {
    return {
      messages: [],
      inputMsg: ''
    }
  },
  onLoad() {
    this.connectSocket()
  },
  methods: {
    connectSocket() {
      socketTask = uni.connectSocket({
        url: 'wss://example.com/chat',
        success: () => {
          socketTask.onMessage(res => {
            this.messages.push(JSON.parse(res.data))
          })
        }
      })
    },
    
    sendMessage() {
      if(this.inputMsg.trim()) {
        const msg = {
          content: this.inputMsg,
          timestamp: Date.now(),
          fromUser: true
        }
        
        socketTask.send({
          data: JSON.stringify(msg),
          success: () => {
            this.messages.push(msg)
            this.inputMsg = ''
          }
        })
      }
    }
  }
}

数据分析与监控

集成统计SDK示例:

// 在App.vue中初始化统计
export default {
  onLaunch() {
    // #ifdef APP-PLUS
    const umeng = uni.requireNativePlugin('UMeng-Analytics')
    umeng.init('your_app_key')
    // #endif
    
    // #ifdef H5
    // 初始化百度统计
    const _hmt = _hmt || []
    ;(function() {
      const hm = document.createElement("script")
      hm.src = "https://hm.baidu.com/hm.js?your_app_key"
      const s = document.getElementsByTagName("script")[0] 
      s.parentNode.insertBefore(hm, s)
    })()
    // #endif
  },
  
  onShow() {
    this.trackPageView()
  },
  
  methods: {
    trackPageView() {
      const pages = getCurrentPages()
      const currentPage = pages[pages.length -1]
      
      // #ifdef APP-PLUS
      const umeng = uni.requireNativePlugin('UMeng-Analytics')
      umeng.trackPageBegin(currentPage.route)
      // #endif
      
      // #ifdef H5
      _hmt.push(['_trackPageview', currentPage.$page.fullPath])
      // #endif
    }
  }
}

持续集成与部署

配置GitHub Actions自动化部署:

name: Build and Deploy

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '14'
    
    - name: Install Dependencies
      run: npm install
      
    - name: Build for Production
      run: npm run build:mp-weixin
      
    - name: Deploy to Weixin Mini Program
      uses: wulabing/wechat-miniprogram-action@v1.1
      with:
        appid: ${{ secrets.MINI_PROGRAM_APPID }}
        version: ${{ github.sha }}
        desc: '自动部署'
        privateKey: ${{ secrets.MINI_PROGRAM_PRIVATE_KEY }}
        projectPath: './dist/build/mp-weixin'

错误监控与日志收集

实现前端错误收集:

// 错误处理拦截器
uni.addInterceptor('request', {
  fail: (err) => {
    this.logError({
      type: 'request_error',
      message: err.errMsg,
      url: err.config.url,
      time: new Date().toISOString()
    })
  }
})

// Vue错误捕获
Vue.config.errorHandler = (err, vm, info) => {
  this.logError({
    type: 'vue_error',
    message: err.message,
    component: vm.$options.name,
    info,
    stack: err.stack,
    time: new Date().toISOString()
  })
}

// 全局错误捕获
window.onerror = function(message, source, lineno, colno, error) {
  logError({
    type: 'window_error',
    message,
    source,
    lineno,
    colno,
    stack: error && error.stack,
    time: new Date().toISOString()
  })
}

// 日志上报方法
logError(data) {
  uni.request({
    url: '/api/logs/error',
    method: 'POST',
    data,
    header: {
      'content-type': 'application/json'
    }
  })
}

国际化实现方案

使用vue-i18n实现多语言支持:

// 在main.js中配置
import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

const i18n = new VueI18n({
  locale: uni.getLocale(), // 获取系统当前语言
  messages: {
    'zh-CN': {
      home: '首页',
      cart: '购物车',
      product: {
        price: '价格',
        addToCart: '加入购物车'
      }
    },
    'en-US': {
      home: 'Home',
      cart: 'Cart',
      product: {
        price: 'Price',
        addToCart: 'Add to Cart'
      }
    }
  }
})

// 在页面中使用
<template>
  <view>
    <text>{{ $t('home') }}</text>
    <text>{{ $t('product.price') }}: ¥{{product.price}}</text>
    <button @click="addToCart">{{ $t('product.addToCart') }}</button>
  </view>
</template>

主题切换功能

实现动态主题切换:

// theme.js
const themes = {
  default: {
    primaryColor: '#ff0000',
    secondaryColor: '#00ff00',
    textColor: '#333333'
  },
  dark: {
    primaryColor: '#990000',
    secondaryColor: '#009900',
    textColor: '#ffffff'
  }
}

export function getTheme(themeName) {
  return themes[themeName] || themes.default
}

// 在Vue中应用主题
computed: {
  themeStyle() {
    return {
      '--primary-color': this.theme.primaryColor,
      '--secondary-color': this.theme.secondaryColor,
      '--text-color': this.theme.textColor
    }
  }
}

// 在模板中使用
<view :style="themeStyle" class="theme-container">
  <!-- 内容 -->
</view>

// CSS中使用变量
.theme-container {
  color: var(--text-color);
  background-color: var(--secondary-color);
}

.button {
  background-color: var(--primary-color);
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌