【源码】Vue3 + Vite 开发移动端商城小程序 UI:手把手从项目初始化到上线

0 阅读4分钟

【源码】Vue3 + Vite 开发移动端商城小程序 UI:手把手从项目初始化到上线

本文详细记录了一个完整移动端商城小程序的 Vue3 开发全过程,包含首页、分类页、商品详情、购物车、个人中心五大核心页面,附完整源码。

一、项目成果展示

先看效果(手机模拟器截图):

📱 首页

  • 顶部搜索栏 + 轮播 Banner(自动轮播 3s)
  • 5 列分类图标入口
  • 限时活动卡片(渐变背景)
  • 热销推荐(2 列卡片网格)
  • 猜你喜欢(列表流)

📱 分类页

  • 左侧分类导航(10 个类目)
  • 右侧商品网格
  • 联动切换,数据实时过滤

📱 商品详情页

  • Sticky 导航栏
  • SKU 选择器(颜色 + 尺码)
  • 数量加减控件
  • 固定底部操作栏(加入购物车 / 立即购买)
  • Toast 动画提示

📱 购物车

  • Pinia 状态管理(加入 / 删除 / 全选 / 编辑模式)
  • 动态数量控制
  • 空购物车引导
  • 底部结算栏

📱 个人中心

  • 渐变用户头部
  • 资产栏(优惠券 / 余额 / 积分)
  • 订单入口(5 种状态)
  • 功能菜单列表

二、技术栈选型

技术版本用途
Vue 3.4^3.4.0核心框架,Composition API + <script setup>
TypeScript^5.3.0类型安全,开发体验提升
Vite 5^5.1.0极速构建,HMR 热更新
Vue Router 4^4.3.0路由管理,支持动态路由
Pinia^2.1.7状态管理,轻量替代 Vuex
SCSS^1.70.0样式预处理器,变量复用

为什么选 Vue3 而不是 UniApp?

  • UniApp 语法与 Vue3 有差异,HBuilderX 生态封闭
  • Vue3 + Vite 可以直接在浏览器预览,调试效率更高
  • 同一套代码,编译后可直接嵌入 UniApp 或微信小程序 web-view

三、项目初始化

# 创建项目
npm create vite@latest shop-basic -- --template vue-ts
cd shop-basic

# 安装依赖
npm install vue vue-router@4 pinia
npm install -D sass @vitejs/plugin-vue vue-tsc

# 启动开发
npm run dev

配置 Vite 支持 SCSS 全局变量注入(无需每个文件单独 import):

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/styles/variables.scss";`
      }
    }
  }
})

四、主题色系统

定义一套统一的 SCSS 变量,后续改主题色只改一处:

// src/styles/variables.scss
$primary: #e93323;          // 主题红(电商经典色)
$primary-light: #ff6b5b;
$primary-dark: #c0281a;
$bg: #f5f5f5;               // 页面背景灰
$text-primary: #333;        // 主文字
$text-secondary: #666;
$text-hint: #999;
$border: #f0f0f0;
$white: #fff;

五、Phone Shell 设计模式

为了在小程序和 H5 都能展示,我们给页面套上一层手机壳:

<!-- App.vue -->
<template>
  <div class="app-container">
    <div class="phone-shell">
      <!-- 状态栏 -->
      <div class="status-bar">
        <span>9:41</span>
        <span>📶 🔋</span>
      </div>
      <!-- 页面内容 -->
      <div class="page-content">
        <router-view />
      </div>
      <!-- TabBar -->
      <div class="tab-bar">
        <!-- 底部导航项 -->
      </div>
    </div>
  </div>
</template>

<style lang="scss">
.app-container {
  background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

.phone-shell {
  width: 390px;
  height: 844px;  // iPhone 12 尺寸
  background: #fff;
  border-radius: 40px;
  overflow: hidden;
  box-shadow: 0 30px 80px rgba(0,0,0,.6);
  display: flex;
  flex-direction: column;
}
</style>

这样每个页面渲染出来就是一个完整的"小程序截图",非常适合做演示和闲鱼上架图。


六、首页完整实现

<!-- src/pages/index/index.vue -->
<template>
  <div class="index-page">
    <!-- 搜索栏 -->
    <div class="search-bar" @click="router.push('/search')">
      <svg width="16" height="16" viewBox="..."/>
      <span class="placeholder">搜索商品</span>
    </div>

    <!-- 轮播图 -->
    <div class="banner">
      <div class="banner-track" :style="{ transform: `translateX(-${bannerIndex * 100}%)` }">
        <div v-for="item in banners" :key="item.id" class="banner-slide">
          <img :src="item.image" />
        </div>
      </div>
      <div class="banner-dots">
        <span v-for="(_, i) in banners" :class="{ active: i === bannerIndex }" @click="bannerIndex = i" />
      </div>
    </div>

    <!-- 分类入口 -->
    <div class="category-entry">
      <div v-for="cat in categories" :key="cat.id" class="cat-item" @click="goCategory(cat.id)">
        <div class="cat-icon" :style="{ background: cat.color }">
          <span>{{ cat.emoji }}</span>
        </div>
        <span>{{ cat.name }}</span>
      </div>
    </div>

    <!-- 热销推荐 -->
    <div class="section">
      <div class="section-header">
        <span class="title">🛒 热销推荐</span>
        <span class="more" @click="goCategory()">查看更多 ›</span>
      </div>
      <div class="goods-grid">
        <div v-for="goods in hotGoods" :key="goods.id" class="goods-card" @click="goDetail(goods.id)">
          <div class="goods-img" :style="{ background: goods.bgColor }">
            <span class="goods-emoji">{{ goods.emoji }}</span>
            <span v-if="goods.tag" class="goods-tag">{{ goods.tag }}</span>
          </div>
          <div class="goods-info">
            <p class="goods-title">{{ goods.title }}</p>
            <div class="goods-price">
              <span class="price">¥{{ goods.price }}</span>
              <span class="origin-price" v-if="goods.originalPrice > goods.price">¥{{ goods.originalPrice }}</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
const bannerIndex = ref(0)
let timer: ReturnType<typeof setInterval>

// 自动轮播
onMounted(() => {
  timer = setInterval(() => {
    bannerIndex.value = (bannerIndex.value + 1) % banners.value.length
  }, 3000)
})
onUnmounted(() => clearInterval(timer))

const banners = ref([...])
const categories = ref([...])
const hotGoods = ref([...])

const goDetail = (id: number) => router.push(`/goods/${id}`)
</script>

七、Pinia 购物车状态管理

核心亮点:Composition API 风格的 Store,支持完整的增删改查:

// src/stores/cart.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export interface CartItem {
  id: number
  goodsId: number
  title: string
  price: number
  quantity: number
  sku: string
  checked: boolean
}

export const useCartStore = defineStore('cart', () => {
  const cartList = ref<CartItem[]>([])

  const checkedItems = computed(() => cartList.value.filter(item => item.checked))
  const totalCount = computed(() =>
    cartList.value.reduce((sum, item) => sum + item.quantity, 0)
  )
  const totalPrice = computed(() =>
    checkedItems.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )
  const isAllChecked = computed(
    () => cartList.value.length > 0 && cartList.value.every(item => item.checked)
  )

  function addItem(goodsId: number, color: string, size: string, qty: number) {
    const existing = cartList.value.find(
      i => i.goodsId === goodsId && i.sku === `${color} / ${size}`
    )
    if (existing) {
      existing.quantity += qty
    } else {
      cartList.value.push({ id: Date.now(), goodsId, title: '...', price: 89, quantity: qty, sku: `${color} / ${size}`, checked: true })
    }
  }

  function toggleCheckAll() {
    const newVal = !isAllChecked.value
    cartList.value.forEach(item => { item.checked = newVal })
  }

  return { cartList, totalCount, totalPrice, isAllChecked, addItem, toggleCheckAll, ... }
})

在组件中使用:

<script setup>
import { useCartStore } from '@/stores/cart'
const cartStore = useCartStore()
const { cartList, totalPrice, isAllChecked } = storeToRefs(cartStore)
</script>

八、商品详情页实现

<!-- src/pages/goods/detail.vue -->
<template>
  <div class="detail-page">
    <!-- SKU 选择 -->
    <div class="sku-section">
      <div class="sku-group">
        <span class="sku-label">颜色</span>
        <div class="sku-options">
          <span v-for="color in goods.colors" :key="color"
            class="sku-tag" :class="{ active: selectedColor === color }"
            @click="selectedColor = color">
            {{ color }}
          </span>
        </div>
      </div>
      <div class="quantity-row">
        <span class="sku-label">数量</span>
        <div class="qty-control">
          <button @click="qty = Math.max(1, qty - 1)">-</button>
          <span>{{ qty }}</span>
          <button @click="qty++">+</button>
        </div>
      </div>
    </div>

    <!-- 底部操作栏 -->
    <div class="action-bar">
      <button class="btn-cart" @click="addCart">加入购物车</button>
      <button class="btn-buy" @click="buyNow">立即购买</button>
    </div>
  </div>

  <transition name="fade">
    <div class="toast" v-if="showToast">{{ toastMsg }}</div>
  </transition>
</template>

<script setup>
import { useCartStore } from '@/stores/cart'
const cartStore = useCartStore()
function addCart() {
  cartStore.addItem(goods.id, selectedColor.value, selectedSize.value, qty.value)
  toast('已加入购物车 🛒')
}
</script>

九、路由配置

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

export default createRouter({
  history: createWebHistory(),
  routes: [
    { path: '/', component: () => import('../pages/index/index.vue') },
    { path: '/category', component: () => import('../pages/category/index.vue') },
    { path: '/goods/:id', component: () => import('../pages/goods/detail.vue') },
    { path: '/cart', component: () => import('../pages/cart/index.vue') },
    { path: '/user', component: () => import('../pages/user/index.vue') },
  ],
})

十、部署与后续扩展

构建生产版本:

npm run build
# 输出到 dist/ 目录,可直接部署到 Vercel / Netlify / 静态托管

接入真实后端:

  • src/api/ 中的接口地址替换为真实 API
  • 接入微信支付 / 支付宝支付
  • 添加用户登录(微信静默授权)

嵌入微信小程序:

  • 使用 uni-app 的 wgt 打包模式
  • 或使用 web-view 直接嵌套 H5 页面

十一、源码获取

完整源码已整理为可直接运行的项目包,包含:

  • ✅ 5 个完整页面(首页 / 分类 / 详情 / 购物车 / 个人中心)
  • ✅ Pinia 状态管理(购物车 + 用户)
  • ✅ SCSS 主题色系统
  • ✅ 手机壳 UI 展示层
  • ✅ Vite 构建配置

闲鱼搜索「源码老李-Vue3通用商城小程序UI」即可找到,价格实惠,支持毕业设计 / 作品集 / 二次开发。


十二、总结

这个项目展示了 Vue3 + Vite 开发移动端商城小程序的完整方案,核心亮点:

  1. Composition API + <script setup> — 代码简洁,逻辑清晰
  2. Pinia 状态管理 — 购物车逻辑完整,支持批量操作
  3. SCSS 变量系统 — 一处改色,全局生效
  4. Phone Shell 模式 — 同时适配小程序展示和 H5 开发
  5. 零后端依赖 — 纯前端 UI,可直接对接任意后端 API

希望这篇文章对你有帮助!如果想要更多功能(订单系统、优惠券、地址管理),可以在此基础上继续扩展。


标签: Vue3 Vite TypeScript Pinia 商城小程序 前端开发 毕业设计 SCSS