Vue3+TypeScript实战:从零搭建一个电商后台管理系统(含完整代码)

Vue3 + TypeScript 实战:从零构建一个电商后台管理系统

最近几年,前端开发领域的技术栈迭代速度令人目不暇接,但 Vue 3 配合 TypeScript 的组合,却稳稳地站住了脚跟,成为了许多中大型项目,尤其是后台管理系统的首选方案。我身边不少从 Vue 2 迁移过来的朋友,最初都对组合式 API 和严格的类型检查感到些许不适应,但真正用上手之后,普遍反馈是“回不去了”——代码的组织性、可维护性和开发体验都有了质的飞跃。如果你已经掌握了 Vue 的基础语法,正跃跃欲试想挑战一个完整的实战项目,那么一个功能完备的电商后台管理系统,无疑是最佳的练手场。它几乎涵盖了现代前端开发的所有核心环节:项目工程化、路由与权限、状态管理、组件封装、接口联调以及性能优化。本文将带你从零开始,手把手搭建这样一个系统,我会分享许多在官方文档里找不到的实战细节和“踩坑”经验。

1. 项目初始化与工程化配置

万事开头难,一个良好的项目开端能避免后续无数麻烦。我们不再使用传统的 Vue CLI,而是选择更快的 Vite 作为构建工具。它不仅启动和热更新速度极快,而且对 TypeScript 的支持是原生的,开箱即用。

首先,打开你的终端,执行以下命令来创建项目:

npm create vue@latest vue3-ts-admin

创建过程中,命令行会交互式地让你选择需要的功能。请务必勾选上 TypeScriptVue RouterPiniaESLintPrettier。至于测试工具,可以根据需要选择 Vitest,这对于我们当前的项目来说是可选的。

项目创建完成后,先别急着写代码。有几项关键的配置需要调整,以确保团队协作和代码质量。

首先,是 TypeScript 的配置 (tsconfig.json)。Vite 生成的默认配置通常够用,但我建议增加一些严格的类型检查选项,这能帮助我们在开发阶段就捕获潜在的错误:

{
  "compilerOptions": {
    // ... 其他配置
    "strict": true,
    "noImplicitAny": true, // 禁止隐式的 any 类型
    "strictNullChecks": true, // 严格的 null 检查
    "types": ["vite/client"] // 确保 Vite 客户端类型被识别
  }
}

其次,是 ESLint 和 Prettier 的整合。这两者一个负责代码质量,一个负责代码格式,搭配使用能保证代码风格统一。安装必要的依赖:

npm install -D eslint-plugin-prettier eslint-config-prettier

然后,在 .eslintrc.cjs 配置文件中,将 prettier 的规则集成进去,避免两者冲突:

module.exports = {
  // ... 其他配置
  extends: [
    // ... 其他扩展
    'plugin:prettier/recommended' // 放在最后,用 Prettier 的规则覆盖格式相关的 ESLint 规则
  ]
}

最后,在 package.json 中添加几个实用的脚本,方便日常开发:

{
  "scripts": {
    "dev": "vite",
    "build": "vue-tsc && vite build", // 先进行类型检查,再构建
    "preview": "vite preview",
    "lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix", // 自动修复可修复的 lint 错误
    "format": "prettier --write ." // 格式化所有文件
  }
}

注意:强烈建议在提交代码前运行 npm run lintnpm run format,或者配置 Git Hooks(如 husky)来自动执行,这是保证代码库整洁的最低成本方式。

2. 核心架构:路由、状态管理与请求封装

一个后台管理系统的骨架,主要由路由、状态管理和网络请求这三部分构成。搭建好这个骨架,后续的功能开发就像往里面填充血肉,会顺畅很多。

2.1 基于角色权限的动态路由设计

电商后台通常涉及多个角色,如超级管理员、商品管理员、订单管理员等。不同角色能访问的页面(路由)和操作权限是不同的。静态地在 router/index.ts 里定义所有路由,然后通过导航守卫来隐藏菜单,并不是最佳实践,因为用户依然可以通过输入 URL 直接访问。更安全的做法是动态路由:用户登录后,根据其角色从后端获取有权限的路由配置,再动态添加到路由实例中。

首先,我们定义路由的元信息类型,用于携带权限数据:

// types/router.d.ts
import { RouteRecordRaw } from 'vue-router';

declare module 'vue-router' {
  interface RouteMeta {
    title: string; // 页面标题,用于显示在浏览器标签和面包屑
    requiresAuth?: boolean; // 是否需要登录
    roles?: string[]; // 允许访问的角色数组,如 ['admin', 'editor']
    icon?: string; // 菜单图标,例如 'el-icon-s-goods'
    hidden?: boolean; // 是否不在侧边栏菜单中显示
    keepAlive?: boolean; // 是否缓存该页面组件
  }
}

export type AppRouteRecordRaw = RouteRecordRaw & {
  meta?: RouteMeta;
  children?: AppRouteRecordRaw[];
};

接着,我们创建两套路由表。一套是静态路由,比如登录页、404页面,这些是所有用户都能访问的。另一套是动态路由,即需要权限才能访问的业务模块路由。

// router/routes.ts
import type { AppRouteRecordRaw } from '@/types/router';

// 静态路由(无需权限)
export const constantRoutes: AppRouteRecordRaw[] = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/login/index.vue'),
    meta: { title: '登录', hidden: true } // hidden 为 true 不会出现在菜单
  },
  {
    path: '/404',
    name: 'NotFound',
    component: () => import('@/views/error-page/404.vue'),
    meta: { title: '404', hidden: true }
  }
];

// 动态路由(需要权限),这里先本地模拟,实际应由后端返回
export const asyncRoutes: AppRouteRecordRaw[] = [
  {
    path: '/',
    component: () => import('@/layouts/BasicLayout.vue'),
    redirect: '/dashboard',
    meta: { title: '首页' },
    children: [
      {
        path: 'dashboard',
        name: 'Dashboard',
        component: () => import('@/views/dashboard/index.vue'),
        meta: { title: '仪表盘', icon: 'dashboard', requiresAuth: true }
      },
      {
        path: 'product',
        name: 'Product',
        redirect: '/product/list',
        meta: { title: '商品管理', icon: 'shopping', roles: ['admin', 'product_manager'] },
        children: [
          {
            path: 'list',
            name: 'ProductList',
            component: () => import('@/views/product/list/index.vue'),
            meta: { title: '商品列表', keepAlive: true }
          },
          {
            path: 'category',
            name: 'ProductCategory',
            component: () => import('@/views/product/category/index.vue'),
            meta: { title: '商品分类' }
          }
        ]
      }
      // ... 更多路由
    ]
  }
];

在路由守卫中,我们实现动态添加路由的逻辑:

// router/permission.ts
import router from './index';
import { useUserStore } from '@/stores/user';
import { usePermissionStore } from '@/stores/permission';

router.beforeEach(async (to, from, next) => {
  const userStore = useUserStore();
  const permissionStore = usePermissionStore();

  // 1. 判断是否需要登录
  if (to.meta.requiresAuth && !userStore.token) {
    next(`/login?redirect=${to.fullPath}`);
    return;
  }

  // 2. 如果已登录,且是首次进入,则获取用户信息和权限路由
  if (userStore.token && !permissionStore.isRoutesAdded) {
    try {
      // 获取用户信息(包含角色)
      await userStore.getUserInfo();
      // 根据角色,过滤出有权限的动态路由
      const accessRoutes = await permissionStore.generateRoutes(userStore.roles);
      // 动态添加到路由实例
      accessRoutes.forEach(route => router.addRoute(route));
      permissionStore.setRoutesAdded(true);
      // 添加完路由后,重定向到目标页面,确保路由生效
      next({ ...to, replace: true });
    } catch (error) {
      // 获取失败,清空token,跳转到登录页
      userStore.resetToken();
      next(`/login?redirect=${to.path}`);
    }
  } else {
    next();
  }
});

2.2 使用 Pinia 进行现代化状态管理

Vuex 4 虽然能用,但 Pinia 才是 Vue 3 的“亲儿子”。它更简洁,完美支持组合式 API 和 TypeScript,并且没有 mutations 的概念,直接修改 state 或者使用 actions 都可以。

我们以管理用户信息和权限的状态为例。首先安装 Pinia:

npm install pinia

然后创建一个用户状态存储:

// stores/user.ts
import { defineStore } from 'pinia';
import { login, getUserInfo, logout } from '@/api/user';
import type { LoginForm, UserInfo } from '@/api/user/types';

interface UserState {
  token: string | null;
  userInfo: Partial<UserInfo> | null;
  roles: string[];
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    token: localStorage.getItem('token'),
    userInfo: null,
    roles: []
  }),
  getters: {
    isLoggedIn: state => !!state.token,
    userName: state => state.userInfo?.name || ''
  },
  actions: {
    async login(loginForm: LoginForm) {
      try {
        const { data } = await login(loginForm);
        this.token = data.token;
        localStorage.setItem('token', data.token);
        // 登录成功后,通常需要获取用户信息
        await this.getUserInfo();
      } catch (error) {
        // 统一在调用处处理错误,这里可以清理状态
        this.resetToken();
        throw error; // 重新抛出错误
      }
    },
    async getUserInfo() {
      if (!this.token) return;
      const { data } = await getUserInfo();
      this.userInfo = data;
      this.roles = data.roles || [];
    },
    async logo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值