Skip to content

企业级项目实战

在本章中,我们将使用 Vue3 构建一个企业级项目,涵盖项目初始化、架构搭建、路由配置、状态管理、API 调用等核心功能。

项目需求

我们需要构建一个企业级的用户管理系统,具有以下功能:

  1. 用户登录/注册
  2. 用户列表管理
  3. 用户详情查看
  4. 用户编辑
  5. 用户删除
  6. 数据分页
  7. 搜索功能
  8. 权限控制

技术栈

  • 框架:Vue 3
  • 构建工具:Vite
  • 路由:Vue Router
  • 状态管理:Pinia
  • HTTP 客户端:Axios
  • UI 库:Element Plus
  • 样式:SCSS
  • 代码规范:ESLint + Prettier

项目初始化

1. 创建项目

bash
npm create vite@latest enterprise-project -- --template vue
cd enterprise-project
npm install

2. 安装依赖

bash
npm install vue-router@4 pinia axios element-plus sass
npm install --save-dev eslint prettier eslint-plugin-vue

项目架构

enterprise-project/
├── public/              # 静态资源
├── src/
│   ├── assets/          # 资源文件
│   │   ├── styles/      # 样式文件
│   │   └── images/      # 图片文件
│   ├── components/      # 通用组件
│   ├── views/           # 页面组件
│   │   ├── auth/        # 认证相关页面
│   │   └── user/        # 用户管理页面
│   ├── router/          # 路由配置
│   ├── stores/          # 状态管理
│   ├── api/             # API 调用
│   ├── utils/           # 工具函数
│   ├── hooks/           # 自定义 hooks
│   ├── App.vue          # 根组件
│   └── main.js          # 入口文件
├── .eslintrc.js         # ESLint 配置
├── .prettierrc.js       # Prettier 配置
├── vite.config.js       # Vite 配置
└── package.json         # 项目配置

核心功能实现

1. 路由配置

javascript
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '../stores/auth'

const routes = [
  {
    path: '/',
    redirect: '/user/list'
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import('../views/auth/Login.vue'),
    meta: {
      requiresGuest: true
    }
  },
  {
    path: '/register',
    name: 'Register',
    component: () => import('../views/auth/Register.vue'),
    meta: {
      requiresGuest: true
    }
  },
  {
    path: '/user',
    name: 'User',
    component: () => import('../views/user/Layout.vue'),
    meta: {
      requiresAuth: true
    },
    children: [
      {
        path: 'list',
        name: 'UserList',
        component: () => import('../views/user/List.vue')
      },
      {
        path: 'create',
        name: 'UserCreate',
        component: () => import('../views/user/Create.vue')
      },
      {
        path: 'edit/:id',
        name: 'UserEdit',
        component: () => import('../views/user/Edit.vue')
      },
      {
        path: 'detail/:id',
        name: 'UserDetail',
        component: () => import('../views/user/Detail.vue')
      }
    ]
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

// 路由守卫
router.beforeEach((to, from, next) => {
  const authStore = useAuthStore()
  const isLoggedIn = authStore.isLoggedIn

  if (to.meta.requiresAuth && !isLoggedIn) {
    next('/login')
  } else if (to.meta.requiresGuest && isLoggedIn) {
    next('/user/list')
  } else {
    next()
  }
})

export default router

2. 状态管理

javascript
// stores/auth.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { login as loginApi, register as registerApi, logout as logoutApi } from '../api/auth'

export const useAuthStore = defineStore('auth', () => {
  const token = ref(localStorage.getItem('token') || null)
  const user = ref(JSON.parse(localStorage.getItem('user') || 'null'))

  const isLoggedIn = computed(() => !!token.value)

  async function login(username, password) {
    try {
      const response = await loginApi(username, password)
      token.value = response.token
      user.value = response.user
      localStorage.setItem('token', response.token)
      localStorage.setItem('user', JSON.stringify(response.user))
      return true
    } catch (error) {
      console.error('登录失败:', error)
      return false
    }
  }

  async function register(userData) {
    try {
      const response = await registerApi(userData)
      return true
    } catch (error) {
      console.error('注册失败:', error)
      return false
    }
  }

  function logout() {
    token.value = null
    user.value = null
    localStorage.removeItem('token')
    localStorage.removeItem('user')
  }

  return {
    token,
    user,
    isLoggedIn,
    login,
    register,
    logout
  }
})

// stores/user.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { getUserList, getUserById, createUser, updateUser, deleteUser } from '../api/user'

export const useUserStore = defineStore('user', () => {
  const users = ref([])
  const currentUser = ref(null)
  const loading = ref(false)
  const error = ref(null)
  const pagination = ref({
    page: 1,
    pageSize: 10,
    total: 0
  })

  async function fetchUsers(params) {
    loading.value = true
    error.value = null
    try {
      const response = await getUserList({
        ...params,
        page: pagination.value.page,
        pageSize: pagination.value.pageSize
      })
      users.value = response.data
      pagination.value.total = response.total
    } catch (err) {
      error.value = '获取用户列表失败'
      console.error(err)
    } finally {
      loading.value = false
    }
  }

  async function fetchUserById(id) {
    loading.value = true
    error.value = null
    try {
      currentUser.value = await getUserById(id)
    } catch (err) {
      error.value = '获取用户详情失败'
      console.error(err)
    } finally {
      loading.value = false
    }
  }

  async function addUser(userData) {
    loading.value = true
    error.value = null
    try {
      await createUser(userData)
      await fetchUsers()
      return true
    } catch (err) {
      error.value = '创建用户失败'
      console.error(err)
      return false
    } finally {
      loading.value = false
    }
  }

  async function editUser(id, userData) {
    loading.value = true
    error.value = null
    try {
      await updateUser(id, userData)
      await fetchUsers()
      return true
    } catch (err) {
      error.value = '更新用户失败'
      console.error(err)
      return false
    } finally {
      loading.value = false
    }
  }

  async function removeUser(id) {
    loading.value = true
    error.value = null
    try {
      await deleteUser(id)
      await fetchUsers()
      return true
    } catch (err) {
      error.value = '删除用户失败'
      console.error(err)
      return false
    } finally {
      loading.value = false
    }
  }

  function setPage(page) {
    pagination.value.page = page
    fetchUsers()
  }

  function setPageSize(pageSize) {
    pagination.value.pageSize = pageSize
    pagination.value.page = 1
    fetchUsers()
  }

  return {
    users,
    currentUser,
    loading,
    error,
    pagination,
    fetchUsers,
    fetchUserById,
    addUser,
    editUser,
    removeUser,
    setPage,
    setPageSize
  }
})

3. API 调用

javascript
// api/request.js
import axios from 'axios'

const request = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
})

// 请求拦截器
request.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  error => {
    console.error('请求错误:', error)
    return Promise.reject(error)
  }
)

// 响应拦截器
request.interceptors.response.use(
  response => {
    return response.data
  },
  error => {
    if (error.response) {
      switch (error.response.status) {
        case 401:
          localStorage.removeItem('token')
          localStorage.removeItem('user')
          window.location.href = '/login'
          break
        case 403:
          console.error('禁止访问')
          break
        case 404:
          console.error('资源不存在')
          break
        case 500:
          console.error('服务器错误')
          break
        default:
          console.error('请求失败')
      }
    } else if (error.request) {
      console.error('网络错误,无法连接到服务器')
    } else {
      console.error('请求配置错误:', error.message)
    }
    return Promise.reject(error)
  }
)

export default request

// api/auth.js
import request from './request'

export const login = (username, password) => {
  return request({
    url: '/auth/login',
    method: 'post',
    data: { username, password }
  })
}

export const register = (userData) => {
  return request({
    url: '/auth/register',
    method: 'post',
    data: userData
  })
}

export const logout = () => {
  return request({
    url: '/auth/logout',
    method: 'post'
  })
}

// api/user.js
import request from './request'

export const getUserList = (params) => {
  return request({
    url: '/users',
    method: 'get',
    params
  })
}

export const getUserById = (id) => {
  return request({
    url: `/users/${id}`,
    method: 'get'
  })
}

export const createUser = (userData) => {
  return request({
    url: '/users',
    method: 'post',
    data: userData
  })
}

export const updateUser = (id, userData) => {
  return request({
    url: `/users/${id}`,
    method: 'put',
    data: userData
  })
}

export const deleteUser = (id) => {
  return request({
    url: `/users/${id}`,
    method: 'delete'
  })
}

4. 页面组件

登录页面

vue
<template>
  <div class="login-container">
    <div class="login-form">
      <h2>登录</h2>
      <el-form :model="loginForm" :rules="rules" ref="loginFormRef">
        <el-form-item label="用户名" prop="username">
          <el-input v-model="loginForm.username" placeholder="请输入用户名"></el-input>
        </el-form-item>
        <el-form-item label="密码" prop="password">
          <el-input v-model="loginForm.password" type="password" placeholder="请输入密码"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleLogin" :loading="loading">登录</el-button>
          <el-button @click="$router.push('/register')">注册</el-button>
        </el-form-item>
      </el-form>
      <div v-if="error" class="error-message">{{ error }}</div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '../../stores/auth'

const router = useRouter()
const authStore = useAuthStore()
const loginFormRef = ref(null)
const loading = ref(false)
const error = ref('')

const loginForm = reactive({
  username: '',
  password: ''
})

const rules = {
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' }
  ],
  password: [
    { required: true, message: '请输入密码', trigger: 'blur' }
  ]
}

async function handleLogin() {
  if (!loginFormRef.value) return
  
  await loginFormRef.value.validate(async (valid) => {
    if (valid) {
      loading.value = true
      error.value = ''
      const success = await authStore.login(loginForm.username, loginForm.password)
      if (success) {
        router.push('/user/list')
      } else {
        error.value = '登录失败,请检查用户名和密码'
      }
      loading.value = false
    }
  })
}
</script>

<style scoped>
.login-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh;
  background-color: #f5f5f5;
}

.login-form {
  width: 400px;
  padding: 20px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.login-form h2 {
  text-align: center;
  margin-bottom: 20px;
  color: #333;
}

.error-message {
  margin-top: 10px;
  color: red;
  text-align: center;
}
</style>

用户列表页面

vue
<template>
  <div class="user-list">
    <div class="header">
      <h2>用户管理</h2>
      <el-button type="primary" @click="$router.push('/user/create')">新增用户</el-button>
    </div>
    
    <div class="search-bar">
      <el-input
        v-model="searchQuery"
        placeholder="搜索用户名或邮箱"
        style="width: 300px"
        @keyup.enter="handleSearch"
      >
        <template #append>
          <el-button @click="handleSearch"><el-icon><Search /></el-icon></el-button>
        </template>
      </el-input>
    </div>
    
    <el-table :data="userStore.users" style="width: 100%" v-loading="userStore.loading">
      <el-table-column prop="id" label="ID" width="80"></el-table-column>
      <el-table-column prop="username" label="用户名"></el-table-column>
      <el-table-column prop="email" label="邮箱"></el-table-column>
      <el-table-column prop="createdAt" label="创建时间" width="180"></el-table-column>
      <el-table-column label="操作" width="180">
        <template #default="scope">
          <el-button size="small" @click="viewUser(scope.row.id)">查看</el-button>
          <el-button size="small" type="primary" @click="editUser(scope.row.id)">编辑</el-button>
          <el-button size="small" type="danger" @click="deleteUser(scope.row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    
    <div class="pagination">
      <el-pagination
        v-model:current-page="userStore.pagination.page"
        v-model:page-size="userStore.pagination.pageSize"
        :page-sizes="[10, 20, 50, 100]"
        layout="total, sizes, prev, pager, next, jumper"
        :total="userStore.pagination.total"
        @size-change="userStore.setPageSize"
        @current-change="userStore.setPage"
      ></el-pagination>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '../../stores/user'
import { Search } from '@element-plus/icons-vue'

const router = useRouter()
const userStore = useUserStore()
const searchQuery = ref('')

onMounted(() => {
  userStore.fetchUsers()
})

function handleSearch() {
  userStore.fetchUsers({ keyword: searchQuery.value })
}

function viewUser(id) {
  router.push(`/user/detail/${id}`)
}

function editUser(id) {
  router.push(`/user/edit/${id}`)
}

function deleteUser(id) {
  ElMessageBox.confirm('确定要删除这个用户吗?', '警告', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  })
  .then(async () => {
    const success = await userStore.removeUser(id)
    if (success) {
      ElMessage.success('删除成功')
    } else {
      ElMessage.error('删除失败')
    }
  })
  .catch(() => {
    // 取消删除
  })
}
</script>

<style scoped>
.user-list {
  padding: 20px;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.header h2 {
  margin: 0;
  color: #333;
}

.search-bar {
  margin-bottom: 20px;
}

.pagination {
  margin-top: 20px;
  display: flex;
  justify-content: flex-end;
}
</style>

5. 布局组件

vue
<template>
  <div class="layout">
    <el-container>
      <el-aside width="200px">
        <div class="logo">
          <h3>用户管理系统</h3>
        </div>
        <el-menu :default-active="activeMenu" class="el-menu-vertical-demo">
          <el-menu-item index="/user/list">
            <el-icon><User /></el-icon>
            <span>用户列表</span>
          </el-menu-item>
          <el-menu-item index="/user/create">
            <el-icon><Plus /></el-icon>
            <span>新增用户</span>
          </el-menu-item>
        </el-menu>
      </el-aside>
      <el-container>
        <el-header>
          <div class="header-right">
            <span>{{ authStore.user?.username }}</span>
            <el-button @click="handleLogout">退出登录</el-button>
          </div>
        </el-header>
        <el-main>
          <router-view></router-view>
        </el-main>
      </el-container>
    </el-container>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '../../stores/auth'
import { User, Plus } from '@element-plus/icons-vue'

const route = useRoute()
const router = useRouter()
const authStore = useAuthStore()

const activeMenu = computed(() => {
  return route.path
})

function handleLogout() {
  authStore.logout()
  router.push('/login')
}
</script>

<style scoped>
.layout {
  height: 100vh;
  overflow: hidden;
}

.logo {
  padding: 20px;
  background-color: #409eff;
  color: white;
  text-align: center;
}

.logo h3 {
  margin: 0;
  font-size: 18px;
}

.el-header {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 0 20px;
  background-color: #f5f7fa;
  border-bottom: 1px solid #e4e7ed;
}

.header-right {
  display: flex;
  align-items: center;
  gap: 10px;
}

.el-main {
  padding: 20px;
  overflow-y: auto;
}
</style>

项目构建与部署

1. 构建项目

bash
npm run build

2. 部署项目

可以将构建后的 dist 目录部署到任何静态文件服务器,如 Nginx、Apache 等。

Nginx 配置示例

nginx
server {
  listen 80;
  server_name example.com;
  
  location / {
    root /path/to/dist;
    index index.html;
    try_files $uri $uri/ /index.html;
  }
}

技术要点

  1. 项目架构:采用模块化、组件化的架构设计,便于维护和扩展。

  2. 状态管理:使用 Pinia 管理全局状态,包括用户认证和用户数据。

  3. 路由管理:使用 Vue Router 实现页面导航和路由守卫。

  4. API 调用:封装 Axios 实现 API 调用,添加请求和响应拦截器。

  5. UI 组件:使用 Element Plus 构建美观的用户界面。

  6. 表单验证:使用 Element Plus 的表单验证功能。

  7. 分页功能:实现数据分页和搜索功能。

  8. 权限控制:通过路由守卫实现基本的权限控制。

  9. 代码规范:使用 ESLint 和 Prettier 保证代码质量。

通过这个企业级项目,我们学习了如何使用 Vue3 及其生态系统构建一个完整的应用,包括项目初始化、架构设计、核心功能实现和部署等。这为我们未来开发更复杂的 Vue 应用打下了坚实的基础。

© 2026 编程马·菜鸟教程 版权所有