阿里云主机折上折
  • 微信号
Current Site:Index > Develop a cross-platform e-commerce application

Develop a cross-platform e-commerce application

Author:Chuan Chen 阅读数:35432人阅读 分类: uni-app

Cross-platform E-commerce Application Development Background

The development of e-commerce applications requires consideration of multi-platform adaptation issues. Traditional development methods require separate development for iOS, Android, Web, and other platforms, resulting in high costs and maintenance difficulties. uni-app, as a cross-platform framework based on Vue.js, allows for one-time development and simultaneous release to multiple platforms, significantly improving development efficiency. A typical e-commerce application usually includes core functional modules such as product display, shopping cart, order management, and payment.

Project Initialization and Configuration

When creating a uni-app project using HBuilderX, selecting the default template can quickly set up the basic structure. The project directory includes pages for page files, static for static resources, and App.vue as the application entry file. The manifest.json file configures basic information such as the application name and icon.

// Example manifest.json configuration
{
  "name": "E-commerce Store",
  "appid": "",
  "description": "",
  "versionName": "1.0.0",
  "versionCode": "100",
  "transformPx": false,
  "app-plus": {
    "splashscreen": {
      "alwaysShowBeforeRender": true,
      "autoclose": true,
      "delay": 0
    }
  }
}

Core Page Development

Homepage Development

The homepage typically includes modules such as a carousel, product categories, and recommended products. The swiper component in uni-app is used to implement the carousel effect:

<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: 'Phones', icon: '/static/phone.png'},
        {id: 2, name: 'Computers', icon: '/static/computer.png'}
      ]
    }
  },
  methods: {
    navToCategory(id) {
      uni.navigateTo({
        url: `/pages/category/list?id=${id}`
      })
    }
  }
}
</script>

Product Detail Page

The product detail page needs to display product images, prices, specifications, reviews, and other information. The uni-popup component is used to implement the specification selection 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">Sold {{goods.sales}} items</view>
    </view>
    
    <view class="spec-section" @click="showSpecPopup">
      <text>Select Specifications</text>
      <uni-icons type="arrowright"></uni-icons>
    </view>
    
    <uni-popup ref="specPopup" type="bottom">
      <view class="spec-popup">
        <!-- Specification selection content -->
      </view>
    </uni-popup>
  </view>
</template>

Shopping Cart Functionality Implementation

The shopping cart needs to manage product selection, quantity modification, and select-all interactions. Vuex is used for state management:

// 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
}

Shopping cart page implementation:

<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">Select All</checkbox>
      <view class="total">
        Total: ¥{{totalPrice}}
      </view>
      <button @click="checkout">Checkout ({{selectedCount}})</button>
    </view>
  </view>
</template>

Order and Payment Process

Order confirmation page displays shipping address, product list, and discount information:

<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>Please select a shipping address</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>Total: ¥{{totalAmount}}</text>
      <button @click="createOrder">Place Order</button>
    </view>
  </view>
</template>

Payment process handling:

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: 'Failed to create order', icon: 'none' })
    }
  },
  
  payOrder(orderId) {
    uni.requestPayment({
      provider: 'wxpay',
      orderInfo: { orderId },
      success: () => {
        uni.redirectTo({
          url: `/pages/order/result?orderId=${orderId}`
        })
      },
      fail: (err) => {
        console.error('Payment failed', err)
      }
    })
  }
}

Multi-platform Adaptation Handling

Different platforms require handling style and functionality differences:

/* Conditional compilation for platform differences */
/* #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 */

Platform-specific API call example:

// WeChat Mini Program user info retrieval
// #ifdef MP-WEIXIN
uni.getUserProfile({
  desc: 'For member profile completion',
  success: (res) => {
    this.userInfo = res.userInfo
  }
})
// #endif

// Native functionality in APP
// #ifdef APP-PLUS
plus.share.sendWithSystem({
  content: 'Share content',
  href: 'https://example.com'
})
// #endif

Performance Optimization Strategies

  1. Image lazy loading:
<image lazy-load :src="item.image" mode="aspectFill"></image>
  1. Paginated product list loading:
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. Using subpackage loading:
// pages.json
{
  "subPackages": [
    {
      "root": "subpackageA",
      "pages": [
        {
          "path": "goods/list",
          "style": {}
        }
      ]
    }
  ]
}

Data Caching Strategy

Using uni-app's storage API for data caching:

// Retrieve cached data
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
  }
}

// Update cache
async updateCache(data) {
  await uni.setStorage({
    key: 'goodsCache',
    data: {
      data,
      timestamp: Date.now()
    }
  })
}

User Feedback and Review System

Product review component implementation:

<template>
  <view class="review-section">
    <view class="review-header">
      <text>Product Reviews ({{reviews.length}})</text>
      <text @click="showAllReviews">View All</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>

Real-time Communication Functionality

Integrating WebSocket for customer service:

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 = ''
          }
        })
      }
    }
  }
}

Data Analysis and Monitoring

Integrating analytics SDK example:

// Initialize analytics in App.vue
export default {
  onLaunch() {
    // #ifdef APP-PLUS
    const umeng = uni.requireNativePlugin('UMeng-Analytics')
    umeng.init('your_app_key')
    // #endif
    
    // #ifdef H5
    // Initialize Baidu Analytics
    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
    }
  }
}

Continuous Integration and Deployment

Configuring GitHub Actions for automated deployment:

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: 'Automated deployment'
        privateKey: ${{ secrets.MINI_PROGRAM_PRIVATE_KEY }}
        projectPath: './dist/build/mp-weixin'

Error Monitoring and Log Collection

Implementing frontend error collection:

// Error handling interceptor
uni.addInterceptor('request', {
  fail: (err) => {
    this.logError({
      type: 'request_error',
      message: err.errMsg,
      url: err.config.url,
      time: new Date().toISOString()
    })
  }
})

// Vue error capture
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()
  })
}

// Global error capture
window.onerror = function(message, source, lineno, colno, error) {
  logError({
    type: 'window_error',
    message,
    source,
    lineno,
    colno,
    stack: error && error.stack,
    time: new Date().toISOString()
  })
}

// Log reporting method
logError(data) {
  uni.request({
    url: '/api/logs/error',
    method: 'POST',
    data,
    header: {
      'content-type': 'application/json'
    }
  })
}

Internationalization Implementation

Using vue-i18n for multilingual support:

// Configuration in main.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

const i18n = new VueI18n({
  locale: uni.getLocale(), // Get system current language
  messages: {
    'zh-CN': {
      home: 'Home',
      cart: 'Cart',
      product: {
        price: 'Price',
        addToCart: 'Add to Cart'
      }
    },
    'en-US': {
      home: 'Home',
      cart: 'Cart',
      product: {
        price: 'Price',
        addToCart: 'Add to Cart'
      }
    }
  }
})

// Usage in pages
<template>
  <view>
    <text>{{ $t('home') }}</text>
    <text>{{ $t('product.price') }}: ¥{{product.price}}</text>
    <button @click="addToCart">{{ $t('product.addToCart') }}</button>
  </view>
</template>

Theme Switching Functionality

Implementing dynamic theme switching:

// 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
}

// Applying theme in Vue
computed: {
  themeStyle() {
    return {
      '--primary-color': this.theme.primaryColor,
      '--secondary-color': this.theme.secondaryColor,
      '--text-color': this.theme.textColor
    }
  }
}

// Usage in template
<view :style="themeStyle" class="theme-container">
  <!-- Content -->
</view>

// Using variables in CSS
.theme-container {
  color: var(--text-color);
  background-color: var(--secondary-color);
}

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

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

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.