Skip to content

前端快速开始

快速了解前端项目结构和开发流程。

项目结构

vue-vben-admin/
├── apps/                    # 应用目录
│   ├── web-antd/           # Ant Design 版本
│   ├── web-ele/            # Element Plus 版本
│   ├── web-naive/          # Naive UI 版本
│   └── backend-mock/       # Mock 后端
├── packages/                # 公共包
│   ├── @core/              # 核心包
│   ├── effects/            # 副作用包
│   ├── utils/              # 工具包
│   └── ...
└── pnpm-workspace.yaml     # Pnpm 工作区配置

技术栈

  • Vue 3 - 渐进式 JavaScript 框架
  • TypeScript 5 - JavaScript 的超集
  • Vite 5 - 下一代前端构建工具
  • Pinia 2 - Vue 状态管理
  • Vue Router 4 - Vue 路由
  • Ant Design Vue 4 - UI 组件库
  • Axios - HTTP 客户端
  • ECharts 5 - 数据可视化
  • Tailwind CSS - 原子化 CSS 框架

快速开始

1. 环境准备

确保已安装:

  • Node.js >= 18.0
  • pnpm >= 8.0
bash
# 安装 pnpm
npm i -g corepack
corepack enable

2. 安装依赖

bash
cd vue-vben-admin
pnpm install

3. 启动开发服务器

bash
# 启动 Ant Design 版本
pnpm dev:antd

# 或使用通用命令
pnpm dev

访问 http://localhost:5173

4. 构建生产版本

bash
# 构建 Ant Design 版本
pnpm build:antd

# 构建所有版本
pnpm build

开发流程

1. 创建页面

apps/web-antd/src/views 目录下创建页面:

vue
<!-- src/views/user/list.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getUserList } from '@/api/user'
import type { User } from '@/types/user'

const users = ref<User[]>([])
const loading = ref(false)

const fetchUsers = async () => {
  loading.value = true
  try {
    const { data } = await getUserList()
    users.value = data
  } finally {
    loading.value = false
  }
}

onMounted(() => {
  fetchUsers()
})
</script>

<template>
  <div class="user-list">
    <a-table 
      :columns="columns" 
      :data-source="users" 
      :loading="loading"
    />
  </div>
</template>

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

2. 配置路由

apps/web-antd/src/router/modules 目录下创建路由:

typescript
// src/router/modules/user.ts
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/user',
    name: 'User',
    component: () => import('@/layouts/default/index.vue'),
    meta: {
      title: '用户管理',
      icon: 'user',
    },
    children: [
      {
        path: 'list',
        name: 'UserList',
        component: () => import('@/views/user/list.vue'),
        meta: {
          title: '用户列表',
        },
      },
    ],
  },
]

export default routes

3. 创建 API

apps/web-antd/src/api 目录下创建 API:

typescript
// src/api/user.ts
import { request } from '@/utils/request'
import type { User } from '@/types/user'

export interface UserListParams {
  page?: number
  size?: number
  keyword?: string
}

export interface UserListResult {
  list: User[]
  total: number
}

// 获取用户列表
export function getUserList(params?: UserListParams) {
  return request.get<UserListResult>('/api/system/user/list', { params })
}

// 获取用户详情
export function getUserById(id: number) {
  return request.get<User>(`/api/system/user/${id}`)
}

// 新增用户
export function createUser(data: Partial<User>) {
  return request.post('/api/system/user', data)
}

// 更新用户
export function updateUser(data: User) {
  return request.put('/api/system/user', data)
}

// 删除用户
export function deleteUser(id: number) {
  return request.delete(`/api/system/user/${id}`)
}

4. 定义类型

apps/web-antd/src/types 目录下定义类型:

typescript
// src/types/user.ts
export interface User {
  id: number
  username: string
  nickname: string
  phone: string
  email: string
  status: number
  createTime: string
  updateTime: string
}

export interface UserForm {
  username: string
  password?: string
  nickname: string
  phone: string
  email: string
  status: number
}

5. 状态管理

apps/web-antd/src/store/modules 目录下创建 Store:

typescript
// src/store/modules/user.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { User } from '@/types/user'
import { getUserInfo } from '@/api/user'

export const useUserStore = defineStore('user', () => {
  const userInfo = ref<User | null>(null)
  const token = ref<string>('')

  // 获取用户信息
  const fetchUserInfo = async () => {
    const { data } = await getUserInfo()
    userInfo.value = data
  }

  // 设置 Token
  const setToken = (newToken: string) => {
    token.value = newToken
  }

  // 清除用户信息
  const clearUserInfo = () => {
    userInfo.value = null
    token.value = ''
  }

  return {
    userInfo,
    token,
    fetchUserInfo,
    setToken,
    clearUserInfo,
  }
})

常用功能

表格组件

vue
<script setup lang="ts">
import { ref } from 'vue'
import type { TableColumn } from 'ant-design-vue'

const columns: TableColumn[] = [
  { title: 'ID', dataIndex: 'id', width: 80 },
  { title: '用户名', dataIndex: 'username' },
  { title: '昵称', dataIndex: 'nickname' },
  { title: '手机号', dataIndex: 'phone' },
  { title: '邮箱', dataIndex: 'email' },
  { title: '状态', dataIndex: 'status', width: 100 },
  { title: '操作', key: 'action', width: 200 },
]

const dataSource = ref([])
</script>

<template>
  <a-table 
    :columns="columns" 
    :data-source="dataSource"
    :pagination="{ pageSize: 10 }"
  >
    <template #bodyCell="{ column, record }">
      <template v-if="column.key === 'action'">
        <a-space>
          <a-button type="link" @click="handleEdit(record)">编辑</a-button>
          <a-button type="link" danger @click="handleDelete(record)">删除</a-button>
        </a-space>
      </template>
    </template>
  </a-table>
</template>

表单组件

vue
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { FormInstance } from 'ant-design-vue'

const formRef = ref<FormInstance>()
const formData = reactive({
  username: '',
  password: '',
  nickname: '',
  phone: '',
  email: '',
})

const rules = {
  username: [{ required: true, message: '请输入用户名' }],
  password: [{ required: true, message: '请输入密码' }],
  phone: [{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' }],
  email: [{ type: 'email', message: '邮箱格式不正确' }],
}

const handleSubmit = async () => {
  await formRef.value?.validate()
  // 提交表单
}
</script>

<template>
  <a-form
    ref="formRef"
    :model="formData"
    :rules="rules"
    :label-col="{ span: 6 }"
    :wrapper-col="{ span: 18 }"
  >
    <a-form-item label="用户名" name="username">
      <a-input v-model:value="formData.username" />
    </a-form-item>
    
    <a-form-item label="密码" name="password">
      <a-input-password v-model:value="formData.password" />
    </a-form-item>
    
    <a-form-item label="昵称" name="nickname">
      <a-input v-model:value="formData.nickname" />
    </a-form-item>
    
    <a-form-item label="手机号" name="phone">
      <a-input v-model:value="formData.phone" />
    </a-form-item>
    
    <a-form-item label="邮箱" name="email">
      <a-input v-model:value="formData.email" />
    </a-form-item>
    
    <a-form-item :wrapper-col="{ offset: 6 }">
      <a-space>
        <a-button type="primary" @click="handleSubmit">提交</a-button>
        <a-button @click="formRef?.resetFields()">重置</a-button>
      </a-space>
    </a-form-item>
  </a-form>
</template>

弹窗组件

vue
<script setup lang="ts">
import { ref } from 'vue'

const visible = ref(false)
const formData = ref({})

const showModal = (record?: any) => {
  if (record) {
    formData.value = { ...record }
  }
  visible.value = true
}

const handleOk = async () => {
  // 提交表单
  visible.value = false
}

defineExpose({ showModal })
</script>

<template>
  <a-modal
    v-model:open="visible"
    title="用户信息"
    @ok="handleOk"
  >
    <!-- 表单内容 -->
  </a-modal>
</template>

请求封装

typescript
// src/utils/request.ts
import axios from 'axios'
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { message } from 'ant-design-vue'
import { useUserStore } from '@/store/modules/user'

const instance: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  timeout: 10000,
})

// 请求拦截器
instance.interceptors.request.use(
  (config) => {
    const userStore = useUserStore()
    if (userStore.token) {
      config.headers.Authorization = userStore.token
    }
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
instance.interceptors.response.use(
  (response: AxiosResponse) => {
    const { code, data, message: msg } = response.data
    
    if (code === 200) {
      return data
    } else {
      message.error(msg || '请求失败')
      return Promise.reject(new Error(msg))
    }
  },
  (error) => {
    if (error.response?.status === 401) {
      // 未登录,跳转到登录页
      const userStore = useUserStore()
      userStore.clearUserInfo()
      window.location.href = '/login'
    } else {
      message.error(error.message || '网络错误')
    }
    return Promise.reject(error)
  }
)

export const request = instance

环境变量

.env

env
# 应用名称
VITE_APP_TITLE=Vben Admin

# 应用描述
VITE_APP_DESC=企业级管理系统框架

.env.development

env
# API 地址
VITE_API_URL=http://localhost:8080

# 是否开启 Mock
VITE_USE_MOCK=false

.env.production

env
# API 地址
VITE_API_URL=https://api.example.com

# 是否开启 Mock
VITE_USE_MOCK=false

常用命令

bash
# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev

# 构建生产版本
pnpm build

# 预览构建结果
pnpm preview

# 代码检查
pnpm lint

# 代码格式化
pnpm format

# 类型检查
pnpm type-check

开发建议

最佳实践

  1. 使用 TypeScript 编写代码
  2. 使用 Composition API
  3. 合理拆分组件
  4. 使用 Pinia 管理状态
  5. 使用 Vue Router 管理路由
  6. 使用 Axios 封装请求
  7. 使用 ESLint 和 Prettier

注意事项

  1. 避免在 setup 中使用 this
  2. 合理使用 ref 和 reactive
  3. 注意内存泄漏问题
  4. 避免过度优化
  5. 注意 SEO 优化

调试技巧

Vue DevTools

安装 Vue DevTools 浏览器插件,可以:

  • 查看组件树
  • 查看组件状态
  • 查看路由信息
  • 查看 Pinia 状态
  • 性能分析

控制台调试

typescript
// 开发环境下打印日志
if (import.meta.env.DEV) {
  console.log('用户信息:', userInfo)
}

网络调试

使用浏览器开发者工具的 Network 标签:

  • 查看请求和响应
  • 检查请求头和响应头
  • 查看请求耗时
  • 模拟网络状况

下一步

相关链接

MIT License