本文目标:你不需要提前懂任何东西,打开电脑,跟着一步步做,最终能搭出一个带接口请求、能用 Element Plus 组件库的前端项目。


一、先搞清楚我们要用什么

前端发展到现在,工具链已经比较成熟了。本文选的是一套最适合新手、也是国内企业用得最多的组合:

工具 干什么的 一句话理解
Node.js 前端的运行环境 就像手机里的操作系统,没有它什么都跑不了
Vue 3 前端框架 帮你用数据驱动页面,不用自己操作 DOM
Vite 构建工具 把代码打包、热更新,开发体验极快
Element Plus UI 组件库 提供现成的按钮、表格、表单,不用自己写 CSS
Axios HTTP 请求库 负责和后台接口打交道

一个比喻帮你记住这五样东西的关系:

你要开一家餐厅。

  • Node.js = 厨房(运行环境)

  • Vue = 你做菜的流程规范(框架)

  • Vite = 你的厨具套装(构建工具,开火就能用)

  • Element Plus = 半成品食材包(组件库,拿来就用)

  • Axios = 外卖配送员(负责把菜送到客人手里 / 从供应商取货)


二、装 Node.js(第一步,也是最容易卡住的一步)

2.1 下载安装

去 Node.js 官网:Node.js — Run JavaScript Everywhere

你会看到两个版本:

  • LTS(长期支持版):稳定,推荐新手选这个

  • Current(最新版):功能新但可能有坑

直接下 LTS 版本,双击安装,一路点"下一步"就行。

2.2 验证装好了没有

打开终端(Win 按 Win+R 输入 cmd,Mac 打开"终端"),输入:

node -v

如果出来一串版本号(比如 v20.11.0),说明装好了。

再输入:

npm -v

出来版本号(比如 10.2.4),说明 npm(Node 自带的包管理器)也 OK 了。

npm 是什么? 你可以把它理解成"应用商店"——你需要什么库(比如 Vue、Axios),敲一行命令它就帮你下载好。


三、创建你的第一个项目

3.1 用 Vite 一键生成项目

在终端里 cd 到你放代码的目录,然后执行:

npm create vite@latest my-first-project -- --template vue

解释一下这行命令:

  • npm create vite@latest:用最新版 Vite 创建项目

  • my-first-project:项目文件夹名字,可以随便改

  • -- --template vue:指定用 Vue 模板

执行后会问你几个问题,一路回车就行。

3.2 进项目、装依赖

cd my-first-project
npm install

npm install 会根据 package.json 文件里记录的清单,把所有需要的库下载到 node_modules 文件夹里。

这步容易遇到的问题: 如果下载很慢或者失败,是因为 npm 默认从国外服务器下载。换成国内镜像:

npm config set registry https://registry.npmmirror.com

然后再执行 npm install,速度起飞。

3.3 跑起来

npm run dev

终端会出现类似这样的提示:

  VITE v5.0.0  ready in 300 ms
​
  ➜  Local:   http://localhost:5173/

打开浏览器,访问 http://localhost:5173/,看到一个 Vue 的欢迎页面,恭喜你,第一个前端项目跑通了!

3.4 项目结构看一眼就够了

my-first-project/
├── index.html          # 入口 HTML,单页应用就这一个页面
├── package.json        # 项目配置,记录了装了哪些依赖
├── vite.config.js      # Vite 的配置文件
├── src/
│   ├── main.js         # 程序的入口,Vue 应用在这里启动
│   ├── App.vue         # 根组件,所有页面都套在这个壳里
│   ├── components/     # 放组件的文件夹
│   ├── views/          # 放页面的文件夹(需要自己建)
│   └── assets/         # 放图片、样式等静态资源
└── node_modules/       # 所有下载的依赖包都在这里(别手动改)

四、理解 Vue 的核心:组件和生命周期

4.1 .vue 文件长什么样

每个 .vue 文件就是一个组件。一个组件由三部分组成:

<template>
  <!-- HTML:页面上显示什么 -->
  <div>
    <h1>{{ title }}</h1>
    <button @click="handleClick">点我</button>
  </div>
</template>
​
<script setup>
// JavaScript:页面逻辑
import { ref } from 'vue'
​
const title = ref('你好,世界')
​
function handleClick() {
  title.value = '你点了我一下'
}
</script>
​
<style scoped>
/* CSS:样式,scoped 表示只在这个组件生效 */
h1 {
  color: #409eff;
}
</style>

三个关键点:

  • {{ }} 双花括号:把 JS 里的数据渲染到页面上

  • ref():让数据变成"响应式"的——数据一变,页面自动跟着变

  • @click:绑定点击事件

4.2 生命周期是什么

一句话:Vue 组件从"出生"到"销毁"的整个过程,就是生命周期。 你在不同阶段可以插入自己的代码。

Vue 3(<script setup> 写法)常用生命周期
<script setup>
import { onMounted, onUnmounted } from 'vue'
​
// 组件挂载完成(页面元素已经渲染到浏览器里了)
onMounted(() => {
  console.log('组件出现了,可以在这里发请求拿数据')
})
​
// 组件销毁前(页面切换走、组件移除时)
onUnmounted(() => {
  console.log('组件要消失了,可以在这里清定时器、取消请求')
})
</script>
created 是什么?

created 是 Vue 2 Options API 里的一个生命周期钩子,在组件实例被创建之后、页面渲染之前触发。Vue 2 里经常在 created 里发请求拿数据:

// Vue 2 Options API 写法
export default {
  data() {
    return { userList: [] }
  },
  created() {
    // 组件实例创建好了,可以访问 data、methods,但 DOM 还没渲染
    this.fetchUsers()
  },
  methods: {
    async fetchUsers() {
      const res = await axios.get('/api/users')
      this.userList = res.data
    }
  }
}
Vue 3 里 created 变成什么了?

Vue 3 的 <script setup> 语法里没有 created 这个钩子,因为 <script setup> 里直接写的代码就是在 created 阶段执行的:

<script setup>
import { ref } from 'vue'
​
// 直接写在这里的代码,就相当于 Vue 2 的 created
// 组件实例创建好、数据初始化完毕时执行
const count = ref(0)
console.log('这行代码在 created 阶段执行')
​
// 如果你确实需要在 created 时机做某事,直接写在这里就行
</script>
完整生命周期图(对比版)
阶段 Vue 2 Options API Vue 3 <script setup> 能做什么
实例创建完成,数据初始化好了,但 DOM 还没渲染 created() 直接写在 <script setup> 顶层 初始化数据、发请求(此时看不到页面元素)
DOM 渲染完成,可以操作页面元素了 mounted() onMounted(() => {}) 发请求、初始化图表、获取 DOM 元素
组件要销毁了 beforeDestroy() onUnmounted(() => {}) 清除定时器、取消未完成的请求

一个生活类比:

生命周期 类比 典型用途
created / <script setup> 顶层 你到公司,走进办公室,但还没开电脑 准备今天要干什么、检查日程
onMounted 你打开电脑,桌面出现了 发请求拉数据、操作页面元素
onUnmounted 你下班,关电脑走人 清除定时器、取消未完成的请求

新手提示: 如果你学的是 Vue 2 教程,会看到大量 created 的用法。如果你用的是 Vue 3 + <script setup>,记得直接把代码写在 <script setup> 顶层就等价于 created。实际开发中,大部分数据请求放在 onMounted 里就够了,因为早几毫秒拿到数据对用户体验几乎没有影响。


五、向后端发请求(Axios)

5.1 安装 Axios

项目根目录下执行:

npm install axios

5.2 第一次发请求

在一个 .vue 组件里试试:

<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'

const userList = ref([])

// 组件加载完就请求数据
onMounted(async () => {
  try {
    const response = await axios.get('https://jsonplaceholder.typicode.com/users')
    userList.value = response.data
    console.log('拿到的数据:', response.data)
  } catch (error) {
    console.error('请求失败了:', error)
  }
})
</script>

<template>
  <div>
    <h2>用户列表</h2>
    <ul>
      <li v-for="user in userList" :key="user.id">
        {{ user.name }} —— {{ user.email }}
      </li>
    </ul>
  </div>
</template>

这里有三个新东西:

  • axios.get(url):发一个 GET 请求到指定地址

  • async / await:因为网络请求需要时间,用 await 等结果回来再继续

  • v-for:Vue 的循环指令,把数组里的每一项渲染出来

5.3 这个 API 是干嘛的

jsonplaceholder.typicode.com 是一个免费的假数据 API,专门给前端新手练习用的。不需要注册,直接发请求就能拿到数据,放心用。

5.4 实战推荐:封装一个请求工具

项目稍微大一点,就不能在每个组件里都写 axios.get。通常的做法是新建一个 src/utils/request.js

import axios from 'axios'
​
// 创建 axios 实例
const request = axios.create({
  baseURL: 'https://your-api-server.com',  // 你的后台地址
  timeout: 10000,  // 10 秒没响应就报超时
})
​
// 请求拦截器:发请求之前可以做的事(比如加 token)
request.interceptors.request.use((config) => {
  const token = localStorage.getItem('token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})
​
// 响应拦截器:收到响应后统一处理(比如统一报错)
request.interceptors.response.use(
  (response) => response.data,
  (error) => {
    console.error('请求出错', error)
    return Promise.reject(error)
  }
)
​
export default request

然后在组件里用:

import request from '@/utils/request'
​
const data = await request.get('/users')
const result = await request.post('/login', { username: 'admin', password: '123456' })

这样全项目的接口请求都走同一个出口,改后台地址、加 token、统一报错都只改这一个文件。


六、Element Plus 组件库

6.1 安装

npm install element-plus

6.2 在 main.js 中引入

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
​
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

6.3 直接用就行了

引完之后,你在任何 .vue 组件里直接用 Element Plus 的标签,不需要额外 import:

<template>
  <!-- 一个按钮 -->
  <el-button type="primary">主要按钮</el-button>
​
  <!-- 一个输入框 -->
  <el-input v-model="inputValue" placeholder="请输入内容" />
​
  <!-- 一个表格 -->
  <el-table :data="tableData" style="width: 100%">
    <el-table-column prop="name" label="姓名" />
    <el-table-column prop="age" label="年龄" />
  </el-table>
​
  <!-- 一个确认弹窗 -->
  <el-button @click="showDialog = true">打开弹窗</el-button>
  <el-dialog v-model="showDialog" title="提示">
    <p>这是一段内容</p>
  </el-dialog>
</template>
​
<script setup>
import { ref } from 'vue'
​
const inputValue = ref('')
const showDialog = ref(false)
const tableData = ref([
  { name: '张三', age: 25 },
  { name: '李四', age: 30 },
])
</script>

6.4 学习 Element Plus 的正确姿势

不要试图把所有组件背下来。 正确的做法是:

需要用什么组件 → 去官网搜 → 复制示例代码 → 改成你自己的数据

Element Plus 官网:一个 Vue 3 UI 框架 | Element Plus

新手上路最常用的五个组件:

组件 标签 什么时候用
按钮 <el-button> 几乎所有页面都需要
输入框 <el-input> 表单、搜索
表格 <el-table> 展示列表数据
弹窗 <el-dialog> 确认操作、填写表单
表单 <el-form> 登录、注册、配置页

七、全流程串起来:一个完整的页面

下面是一个真实的小页面,把上面所有知识点串在一起:

<template>
  <div class="page">
    <!-- 顶部的搜索栏 -->
    <el-input
      v-model="searchKeyword"
      placeholder="搜索用户"
      style="width: 300px; margin-bottom: 16px"
    />
​
    <el-button type="primary" @click="fetchUsers">搜索</el-button>
    <el-button @click="showAddDialog = true">新增用户</el-button>
​
    <!-- 数据表格 -->
    <el-table :data="filteredUsers" style="width: 100%; margin-top: 16px">
      <el-table-column prop="id" label="ID" width="80" />
      <el-table-column prop="name" label="姓名" />
      <el-table-column prop="email" label="邮箱" />
    </el-table>
​
    <!-- 新增用户的弹窗 -->
    <el-dialog v-model="showAddDialog" title="新增用户">
      <el-form :model="newUser">
        <el-form-item label="姓名">
          <el-input v-model="newUser.name" />
        </el-form-item>
        <el-form-item label="邮箱">
          <el-input v-model="newUser.email" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="showAddDialog = false">取消</el-button>
        <el-button type="primary" @click="addUser">确认添加</el-button>
      </template>
    </el-dialog>
  </div>
</template>
​
<script setup>
import { ref, computed, onMounted } from 'vue'
import axios from 'axios'
​
// ----- 数据 -----
const users = ref([])
const searchKeyword = ref('')
const showAddDialog = ref(false)
const newUser = ref({ name: '', email: '' })
​
// ----- 计算属性:根据搜索关键词过滤数据 -----
const filteredUsers = computed(() => {
  if (!searchKeyword.value) return users.value
  return users.value.filter(u => u.name.includes(searchKeyword.value))
})
​
// ----- 方法 -----
async function fetchUsers() {
  const res = await axios.get('https://jsonplaceholder.typicode.com/users')
  users.value = res.data
}
​
async function addUser() {
  // 实际项目里这里发 POST 请求
  users.value.push({
    id: users.value.length + 1,
    name: newUser.value.name,
    email: newUser.value.email,
  })
  showAddDialog.value = false
  newUser.value = { name: '', email: '' }
}
​
// ----- 生命周期:页面加载自动拿数据 -----
onMounted(() => {
  fetchUsers()
})
</script>
​
<style scoped>
.page {
  padding: 24px;
}
</style>

这个页面实现了:页面加载自动拉数据、表格展示、搜索过滤、弹窗新增。你把 jsonplaceholder 的地址换成自己后台的接口地址,就是一个真实可用的业务页面了。


八、跨域问题(CORS)—— 新手必踩的坑

8.1 什么是跨域?为什么会报错?

你第一次把前端(localhost:5173)连上后台(localhost:8080 或其他域名),浏览器控制台大概率会出现这个报错:

Access to XMLHttpRequest at 'http://localhost:8080/api/users'
from origin 'http://localhost:5173' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

先用人话解释:

浏览器就像一个很严格的门卫。你的前端页面运行在 A 域名(比如 localhost:5173),你想去 B 域名(比如 localhost:8080)拿数据。浏览器觉得:"你一个在 A 地盘的人,凭什么去 B 地盘拿东西?万一是恶意攻击呢?" 于是它把你的请求拦了下来。

一句话:跨域不是"你不能发请求",而是"浏览器收到了后台返回的数据,但拒绝把数据交给你的前端代码"。

实际上,请求已经发到了后台,后台也返回了数据,但浏览器的安全策略(同源策略)把数据拦截了。

什么是"同源"? 三个东西必须完全一样才叫同源:

前端页面 后台接口 同源吗?
示例 1 http://localhost:5173 http://localhost:8080 ❌ 端口不同
示例 2 http://localhost:3000 https://api.example.com ❌ 域名、协议都不同
示例 3 http://www.example.com http://www.example.com/api ✅ 完全同源

只要协议、域名、端口有一个不一样,就构成跨域。

8.2 怎么解决?—— 两种方案

方案一:开发阶段用 Vite 代理(前端自己搞定)

这是开发阶段最简单、最常用的方法。原理是:让 Vite 开发服务器冒充中间人,帮你转发请求。 浏览器以为请求发给了和自己同源的 Vite 服务器,实际上 Vite 在背后偷偷转发给了后台。

vite.config.js 配置:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
​
export default defineConfig({
  plugins: [vue()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',  // 你的后台地址
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')  // 可选:去掉 /api 前缀
      }
    }
  }
})

配置完后,前端发请求改为:

// 之前(跨域,会报错)
axios.get('http://localhost:8080/api/users')
​
// 之后(不跨域,Vite 代理转发)
axios.get('/api/users')

转发过程:

浏览器 → 发请求到 /api/users → Vite 开发服务器收到
  → Vite 偷偷转发到 http://localhost:8080/api/users
  → 后台返回数据给 Vite → Vite 返回给浏览器
  → 浏览器:嗯,同源的,放行!

注意: 代理只在开发阶段有效。部署到生产环境后,Vite 代理就不工作了。

方案二:后台配置 CORS(让后台告诉浏览器"我允许跨域")

生产环境部署后,前端的代码直接跑在浏览器里,没有 Vite 帮你转发了。这时候需要后台在返回数据时,在响应头里加上允许跨域的标记

8.3 后端要配置吗?

答案是:要,但分阶段。

阶段 谁来解 怎么解
本地开发 前端开发自己搞定 配 Vite 代理,不需要后台改任何东西
部署上线 后台必须配置 CORS 后端在响应头里加 Access-Control-Allow-Origin

为什么部署上线后前端代理不管用了?

因为 npm run dev 的 Vite 开发服务器只在你本地跑。项目部署后,前端代码被打包成纯 HTML/JS/CSS,部署在 Nginx 或 CDN 上,没有 Vite 了,代理自然就不存在了。

后台常用的 CORS 配置:

以常见的后台框架为例,给后端同事参考(你是前端的话,把这段发给他就行):

Java Spring Boot:

// 在 Controller 上加注解(单个接口)
@CrossOrigin(origins = "http://你的前端域名.com")
@GetMapping("/api/users")
public List<User> getUsers() { ... }
​
// 或者全局配置(推荐)
@Configuration
public class CorsConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")        // 允许哪些接口跨域
                        .allowedOrigins("http://localhost:5173")  // 允许哪个前端地址
                        .allowedMethods("GET", "POST", "PUT", "DELETE")
                        .allowCredentials(true);
            }
        };
    }
}

Node.js Express:

const cors = require('cors')
app.use(cors({
  origin: 'http://localhost:5173',  // 允许哪个前端地址
  credentials: true
}))

Python Flask:

from flask_cors import CORS
CORS(app, origins=['http://localhost:5173'])

Go Gin:

import "github.com/gin-contrib/cors"
r.Use(cors.New(cors.Config{
    AllowOrigins: []string{"http://localhost:5173"},
    AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
}))

8.4 跨域问题排查口诀

遇到跨域报错,按这个顺序检查:

1. 我是在本地开发吗?
   → 是:配 Vite 代理(方案一),不需要找后台
   → 否:跳到第 2 步

2. 项目已经部署上线了 / 打成了生产包?
   → 必须找后台配 CORS(方案二)

3. 后台配了 CORS 还不行?
   → 检查后台 allowOrigins 里写的前端域名和你实际访问的是否一致
   → 检查是否漏了 http:// 前缀
   → 检查是否同时用了 withCredentials: true(带 cookie 时需要)

九、新手最常踩的 5 个坑

1. ref 的值要用 .value

const count = ref(0)
count.value++  // ✅ 正确,JS 里要加 .value
count++        // ❌ 错误,不会生效

但是 <template> 里不用加 .value,Vue 会自动解包:

<!-- 模板里直接用 count 就行 -->
<p>{{ count }}</p>

2. 忘记 v-modelref 的区别

  • ref:定义一个响应式数据

  • v-model:把数据和输入框双向绑定(输入框变了,数据也变;数据变了,输入框也变)

<el-input v-model="username" />
<!-- 相当于:输入框的值 和 username 这个 ref 绑定在一起了 -->

3. Element Plus 组件没有样式

检查一下 main.js 里有没有导入 CSS:

import 'element-plus/dist/index.css'  // 这行必须有!

4. npm install 之后报各种奇怪的错

最常见的原因是 Node 版本不兼容。建议 Node 版本 ≥ 16。 如果还不行,删掉 node_modules 文件夹和 package-lock.json,重新 npm install

5. 开发时接口 404(路径写错了)

// ❌ 如果配了 Vite 代理 /api → localhost:8080
axios.get('http://localhost:8080/api/users')  // 发到了 8080,但浏览器跨域拦截了
​
// ✅ 正确写法:走代理
axios.get('/api/users')  // Vite 自动转发到 localhost:8080

十、总结

你从零开始,按这条路走完了一遍:

装 Node.js → 用 Vite 创建项目 → npm install 装依赖 → npm run dev 跑起来
     ↓
理解 .vue 文件的三部分(template / script / style)
     ↓
掌握 onMounted 生命周期 → 页面加载时发请求拿数据
     ↓
用 Axios 发 GET / POST 请求 → 封装请求工具
     ↓
引入 Element Plus → 直接用现成的按钮、表格、弹窗
     ↓
拼出一个完整页面

接下来你该做什么:

  1. 把上面第七节的完整代码复制到你的项目里,跑起来看看效果

  2. 去 Element Plus 官网,把表格、表单、弹窗的示例都复制一遍试试

  3. 找个免费 API(比如 jsonplaceholder)自己写几个增删改查的页面

  4. 遇到问题就去搜,前端社区资料非常丰富

前端入门没有那么难,关键是先把东西跑起来,再去理解原理。代码敲起来才是最快的路。

更多推荐