Vue项目实战入门:从0到1搭建电商商品列表页

简介: 本文以电商商品列表页为实战案例,详解Vue3项目从需求分析、环境搭建到核心功能实现的完整流程。涵盖组件化开发、Pinia状态管理、Vue Router路由跳转及Axios接口封装,助你掌握Vue工程化开发核心技能,快速构建可落地的前端应用。

项目实战是Vue学习的关键环节,能帮助初学者将零散的基础知识点整合应用,提升工程化实践能力。本文以“电商商品列表页”为实战案例,详细讲解从需求分析、项目搭建、核心功能实现到测试优化的完整流程,提供可直接落地的代码示例与思路。

一、需求分析:明确核心功能与边界

电商商品列表页是用户浏览商品的核心入口,需聚焦核心功能,避免过度设计。明确以下核心需求:

  • 商品列表渲染:展示商品核心信息(图片、名称、价格、销量),布局清晰直观;
  • 条件筛选:支持按价格区间(最低-最高价格)、商品分类(如手机数码、服装鞋帽)筛选;
  • 排序功能:提供多维度排序选项(默认、价格升序、价格降序、销量排序);
  • 分页功能:展示分页控件,支持页码切换、每页条数调整,显示商品总数;
  • 商品跳转:点击商品卡片,跳转到对应的商品详情页(携带商品ID参数)。

二、技术栈选择:轻量高效,适配入门实战

选择成熟、易上手的技术栈,降低实战门槛,同时覆盖Vue核心生态:

  • 前端框架:Vue3 + Vite(Vue3语法更简洁,Vite构建速度快,适合入门);
  • 路由管理:Vue Router 4(实现商品列表页与详情页的路由跳转、参数传递);
  • UI组件库:Element Plus(快速实现筛选表单、分页、卡片等组件,提升开发效率);
  • 接口交互:Axios(封装请求,模拟商品数据获取,对接后端接口);
  • 状态管理:Pinia(轻量易用,管理筛选条件、商品列表、分页信息等全局状态)。

三、项目搭建:分步骤初始化,规范目录结构

按“创建项目→安装依赖→设计目录”的步骤初始化,确保项目结构清晰,符合工程化规范。

1. 创建Vite + Vue3项目

npm create vite@latest vue-shop-list -- --template vue
cd vue-shop-list  # 进入项目目录
npm install       # 安装基础依赖

2. 安装核心依赖包

npm install vue-router@4 element-plus axios pinia

3. 规范项目目录设计

按功能模块划分目录,便于后续维护与扩展:

src/
├── api/           # 接口请求封装(按业务模块拆分)
│   └── goods.js   # 商品相关接口(列表查询等)
├── components/    # 公共可复用组件
│   ├── GoodsCard.vue  # 商品卡片组件(单独渲染单个商品)
│   ├── FilterPanel.vue  # 筛选面板组件(价格、分类筛选)
│   └── Pagination.vue  # 分页组件(复用分页逻辑)
├── router/        # 路由配置目录
│   └── index.js   # 路由规则定义(列表页、详情页)
├── stores/        # Pinia状态管理目录
│   └── goodsStore.js  # 商品相关状态(筛选、列表、分页)
├── views/         # 页面级组件(视图)
│   ├── GoodsList.vue  # 商品列表页(整合各组件)
│   └── GoodsDetail.vue  # 商品详情页(接收商品ID)
├── App.vue        # 根组件(挂载路由出口)
└── main.js        # 入口文件(初始化Vue、路由、Pinia等)

四、核心功能实现:分模块编码,聚焦组件化与状态管理

按“路由→状态→接口→组件”的顺序实现,确保各模块解耦,数据流转清晰。

1. 路由配置:实现页面跳转与参数传递

配置商品列表页与详情页路由,支持通过参数传递商品ID:

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import GoodsList from '../views/GoodsList.vue';  // 商品列表页
import GoodsDetail from '../views/GoodsDetail.vue';  // 商品详情页
// 路由规则
const routes = [
  { path: '/', redirect: '/goods-list' },  // 根路径重定向到列表页
  { 
    path: '/goods-list', 
    component: GoodsList, 
    name: 'GoodsList'  // 命名路由,便于跳转
  },
  { 
    path: '/goods-detail/:id',  // 动态路由,接收商品ID
    component: GoodsDetail, 
    name: 'GoodsDetail' 
  }
];
// 创建路由实例
const router = createRouter({
  history: createWebHistory(),  // HTML5 history模式(无#)
  routes
});
export default router;

2. 状态管理:用Pinia统一管理全局状态

将筛选条件、商品列表、分页信息等全局状态放入Pinia,实现组件间数据共享:

// stores/goodsStore.js
import { defineStore } from 'pinia';
import { getGoodsList } from '../api/goods';  // 引入商品列表接口
// 定义并导出商品相关Store
export const useGoodsStore = defineStore('goods', {
  state: () => ({
    goodsList: [],  // 商品列表数据
    total: 0,       // 商品总数(用于分页)
    pageSize: 10,   // 每页显示条数
    currentPage: 1, // 当前页码
    filterParams: { // 筛选参数
      minPrice: '', // 最低价格
      maxPrice: '', // 最高价格
      categoryId: ''// 商品分类ID
    },
    sortRule: 'default'  // 排序规则:default/price-asc/price-desc/sales
  }),
  actions: {
    // 更新筛选参数:接收新参数,重置页码并重新获取列表
    updateFilterParams(params) {
      this.filterParams = { ...this.filterParams, ...params };
      this.currentPage = 1;  // 筛选条件变化,回到第一页
      this.fetchGoodsList(); // 重新请求商品列表
    },
    // 更新排序规则:切换排序后重新获取列表
    updateSortRule(rule) {
      this.sortRule = rule;
      this.fetchGoodsList();
    },
    // 更新当前页码:切换页码后重新获取列表
    updateCurrentPage(page) {
      this.currentPage = page;
      this.fetchGoodsList();
    },
    // 核心动作:获取商品列表数据(调用接口,更新状态)
    async fetchGoodsList() {
      try {
        // 调用接口,传递分页、筛选、排序参数
        const res = await getGoodsList({
          page: this.currentPage,
          pageSize: this.pageSize,
          ...this.filterParams,  // 展开筛选参数
          sort: this.sortRule    // 排序规则
        });
        // 更新状态:商品列表与总数
        this.goodsList = res.data.list;
        this.total = res.data.total;
      } catch (error) {
        console.error('获取商品列表失败:', error);
        // 可添加错误提示(如Element Plus的Message组件)
      }
    }
  }
});

3. 接口封装:统一请求逻辑,模拟数据交互

封装Axios请求,统一基础路径、超时时间,模拟商品列表接口(实际项目替换为真实后端地址):

// api/goods.js
import axios from 'axios';
// 创建Axios实例,统一配置
const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || '/mock',  // 从环境变量读取基础地址,默认mock
  timeout: 5000  // 超时时间5秒
});
// 封装商品列表查询接口(暴露给外部调用)
export const getGoodsList = (params) => {
  return request.get('/goods/list', { params });  // GET请求,参数拼接到URL
};
// 后续可扩展其他接口(如商品详情查询)

4. 组件实现:拆分公共组件,实现复用与通信

按功能拆分组件,通过Props传递数据、自定义事件触发动作,实现组件解耦。

(1)筛选面板组件(FilterPanel.vue):负责筛选条件输入与提交

接收初始筛选参数,用户输入后触发筛选事件,将参数传递给父组件:

<template>
  <div class="filter-panel">
    <!-- 价格区间筛选 -->
    <el-input-number
      v-model="filterParams.minPrice"
      label="最低价格"
      placeholder="请输入最低价格"
      :min="0"  // 价格不能为负
    ></el-input-number>
    <span>-</span>
    <el-input-number
      v-model="filterParams.maxPrice"
      label="最高价格"
      placeholder="请输入最高价格"
      :min="filterParams.minPrice || 0"  // 最高价格不低于最低价格
    ></el-input-number>
    <!-- 商品分类筛选 -->
    <el-select
      v-model="filterParams.categoryId"
      placeholder="请选择商品分类"
      style="margin-left: 10px;"
    >
      <el-option label="全部商品" value=""></el-option>
      <el-option label="手机数码" value="1"></el-option>
      <el-option label="服装鞋帽" value="2"></el-option>
    </el-select>
    <!-- 筛选按钮:触发筛选事件 -->
    <el-button type="primary" @click="handleFilter" style="margin-left: 10px;">筛选</el-button>
  </div>
</template>
<script setup>
import { defineProps, defineEmits, ref } from 'vue';
// 接收父组件传递的初始筛选参数
const props = defineProps({
  initialParams: {
    type: Object,
    default: () => ({})
  }
});
// 定义筛选事件,向父组件传递筛选参数
const emit = defineEmits(['filter']);
// 本地维护筛选参数(避免直接修改Props)
const filterParams = ref({ ...props.initialParams });
// 筛选按钮点击事件:触发父组件筛选动作
const handleFilter = () => {
  emit('filter', { ...filterParams.value });
};
</script>

(2)商品卡片组件(GoodsCard.vue):单独渲染单个商品,支持跳转

接收单个商品数据,渲染卡片样式,点击后跳转到详情页:

<template>
  <el-card class="goods-card" @click="goToDetail" hover>
    <!-- 商品图片 -->
    <img :src="goods.imgUrl" alt="商品图片" class="goods-img">
    <!-- 商品名称 -->
    <h3 class="goods-name">{{ goods.name }}</h3>
    <!-- 商品价格(保留2位小数) -->
    <p class="goods-price">¥{{ goods.price.toFixed(2) }}</p>
    <!-- 商品销量 -->
    <p class="goods-sales">销量:{{ goods.sales }}</p>
  </el-card>
</template>
<script setup>
import { defineProps } from 'vue';
import { useRouter } from 'vue-router';  // 引入路由钩子
// 接收父组件传递的单个商品数据
const props = defineProps({
  goods: {
    type: Object,
    required: true,
    default: () => ({})
  }
});
// 获取路由实例,用于跳转
const router = useRouter();
// 点击卡片跳转详情页:传递商品ID
const goToDetail = () => {
  router.push({ 
    name: 'GoodsDetail',  // 命名路由跳转
    params: { id: props.goods.id }  // 传递商品ID参数
  });
};
</script>

(3)商品列表页(GoodsList.vue):整合所有组件,实现功能联动

作为父组件,整合筛选面板、排序栏、商品列表、分页组件,通过Pinia实现数据联动:

<template>
  <div class="goods-list-page">
    <h1>商品列表</h1>
    <!-- 筛选面板:传递初始参数,监听筛选事件 -->
    <FilterPanel
      :initial-params="goodsStore.filterParams"
      @filter="goodsStore.updateFilterParams"
    ></FilterPanel>
    <!-- 排序栏:切换排序规则 -->
    <div class="sort-bar">
      <el-button
        :type="goodsStore.sortRule === 'default' ? 'primary' : ''"
        @click="goodsStore.updateSortRule('default')"
      >默认排序</el-button>
      <el-button
        :type="goodsStore.sortRule === 'price-asc' ? 'primary' : ''"
        @click="goodsStore.updateSortRule('price-asc')"
      >价格升序</el-button>
      <el-button
        :type="goodsStore.sortRule === 'price-desc' ? 'primary' : ''"
        @click="goodsStore.updateSortRule('price-desc')"
      >价格降序</el-button>
      <el-button
        :type="goodsStore.sortRule === 'sales' ? 'primary' : ''"
        @click="goodsStore.updateSortRule('sales')"
      >销量排序</el-button>
    </div>
    <!-- 商品列表:循环渲染商品卡片 -->
    <div class="goods-list">
      <GoodsCard
        v-for="goods in goodsStore.goodsList"
        :key="goods.id"  // 唯一标识,避免重复渲染
        :goods="goods"   // 传递单个商品数据
      ></GoodsCard>
    </div>
    <!-- 分页控件:监听页码、每页条数变化 -->
    <el-pagination
      @size-change="handleSizeChange"  // 每页条数变化
      @current-change="goodsStore.updateCurrentPage"  // 页码变化
      :current-page="goodsStore.currentPage"  // 当前页码
      :page-sizes="[5, 10, 20]"  // 可选每页条数
      :page-size="goodsStore.pageSize"  // 当前每页条数
      layout="total, sizes, prev, pager, next, jumper"  // 分页布局
      :total="goodsStore.total"  // 商品总数
      style="margin-top: 20px;"
    ></el-pagination>
  </div>
</template>
<script setup>
import { onMounted } from 'vue';
import { useGoodsStore } from '../stores/goodsStore';  // 引入商品Store
import FilterPanel from '../components/FilterPanel.vue';  // 筛选面板组件
import GoodsCard from '../components/GoodsCard.vue';  // 商品卡片组件
// 获取Pinia状态实例
const goodsStore = useGoodsStore();
// 页面挂载时,初始化获取商品列表
onMounted(() => {
  goodsStore.fetchGoodsList();
});
// 处理每页条数变化:更新条数后重置页码,重新获取列表
const handleSizeChange = (pageSize) => {
  goodsStore.pageSize = pageSize;
  goodsStore.currentPage = 1;
  goodsStore.fetchGoodsList();
};
</script>

五、测试与优化:保障功能稳定,提升用户体验

功能实现后,需通过测试验证完整性,并进行基础优化。

  • 功能测试:逐一验证核心功能——筛选条件是否生效、排序是否正确、分页切换是否流畅、卡片跳转是否携带正确ID;
  • 样式优化:调整组件间距、卡片布局,使用弹性布局(flex)确保在不同屏幕尺寸下自适应显示,提升视觉体验;
  • 性能优化:① 用v-once指令优化商品名称、价格等静态内容的渲染(仅渲染一次);② 用keep-alive缓存筛选面板组件,减少页面切换时的重复渲染;③ 列表渲染时确保key唯一,避免无效DOM操作。

六、项目扩展方向:深化实战,拓展功能边界

基础功能实现后,可通过以下方向拓展,提升项目复杂度与实战价值:

  • 添加商品搜索功能:结合防抖处理(避免频繁输入触发请求),实现关键词搜索;
  • 实现商品收藏功能:用Pinia+localStorage持久化收藏状态,刷新页面后不丢失;
  • 集成Mock.js:模拟完整的后端接口,实现增删改查全流程,无需依赖真实后端;
  • 添加加载状态:在接口请求时显示loading动画,请求失败时显示错误提示,提升用户体验;
  • 适配移动端:使用媒体查询或Element Plus的响应式组件,实现移动端适配。

核心总结

本项目的核心是“组件化思想与状态管理的整合应用”:通过拆分公共组件(筛选、卡片、分页)提升代码复用性,用Pinia统一管理全局状态实现组件联动,用Vue Router实现页面跳转。初学者通过此项目可掌握Vue项目的工程化开发流程,理解“数据驱动视图”的核心逻辑,为后续开发复杂电商项目(如购物车、订单管理)奠定基础。

相关文章
|
1天前
|
云安全 人工智能 算法
以“AI对抗AI”,阿里云验证码进入2.0时代
三层立体防护,用大模型打赢人机攻防战
1278 1
|
8天前
|
编解码 人工智能 自然语言处理
⚽阿里云百炼通义万相 2.6 视频生成玩法手册
通义万相Wan 2.6是全球首个支持角色扮演的AI视频生成模型,可基于参考视频形象与音色生成多角色合拍、多镜头叙事的15秒长视频,实现声画同步、智能分镜,适用于影视创作、营销展示等场景。
675 4
|
1天前
|
机器学习/深度学习 安全 API
MAI-UI 开源:通用 GUI 智能体基座登顶 SOTA!
MAI-UI是通义实验室推出的全尺寸GUI智能体基座模型,原生集成用户交互、MCP工具调用与端云协同能力。支持跨App操作、模糊语义理解与主动提问澄清,通过大规模在线强化学习实现复杂任务自动化,在出行、办公等高频场景中表现卓越,已登顶ScreenSpot-Pro、MobileWorld等多项SOTA评测。
452 2
|
2天前
|
人工智能 Rust 运维
这个神器让你白嫖ClaudeOpus 4.5,Gemini 3!还能接Claude Code等任意平台
加我进AI讨论学习群,公众号右下角“联系方式”文末有老金的 开源知识库地址·全免费
|
1天前
|
存储 弹性计算 安全
阿里云服务器4核8G收费标准和活动价格参考:u2a实例898.20元起,计算型c9a3459.05元起
现在租用阿里云服务器4核8G价格是多少?具体价格及配置详情如下:云服务器ECS通用算力型u2a实例,配备4核8G配置、1M带宽及40G ESSD云盘(作为系统盘),其活动价格为898.20元/1年起;此外,ECS计算型c9a实例4核8G配置搭配20G ESSD云盘,活动价格为3459.05元/1年起。在阿里云的当前活动中,4核8G云服务器提供了多种实例规格供用户选择,不同实例规格及带宽的组合将带来不同的优惠价格。本文为大家解析阿里云服务器4核8G配置的实例规格收费标准与最新活动价格情况,以供参考。
222 150
|
9天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
随机森林是一种基于决策树的集成学习算法,通过构建多棵决策树并结合它们的预测结果来提高准确性和稳定性。其核心思想包括两个随机性:Bootstrap采样(每棵树使用不同的训练子集)和特征随机选择(每棵树分裂时只考虑部分特征)。这种方法能有效处理大规模高维数据,避免过拟合,并评估特征重要性。随机森林的超参数如树的数量、最大深度等可通过网格搜索优化。该算法兼具强大预测能力和工程化优势,是机器学习中的常用基础模型。
350 164