Vue 3 快速入门:从零搭建前后端 CRUD 应用
本文面向想要快速上手 Vue 3 并打通前后端的开发者。通过一个完整示例,涵盖 Vue 3 安装、项目创建、组件编写,以及通过 Axios 调用 FastAPI + SQLite 后端接口,实现对数据库的增删改查。
技术栈: Vue 3 · Vite · FastAPI · SQLite · Axios · Composition API
目录
- [什么是 Vue 3?](#什么是 Vue 3?)
- 安装与环境准备
- [创建 Vue 3 项目](#创建 Vue 3 项目)
- [Vue 单文件组件基础](#Vue 单文件组件基础)
- [后端:FastAPI + SQLite](#后端:FastAPI + SQLite)
- [FastAPI CRUD 完整代码](#FastAPI CRUD 完整代码)
- [Vue 调用后端接口](#Vue 调用后端接口)
- [完整 CRUD 页面示例](#完整 CRUD 页面示例)
- 下一步建议
1. 什么是 Vue 3?
Vue 3 是 Vue.js 的最新主要版本,相比 Vue 2 有三项关键提升:
| 维度 | Vue 2 | Vue 3 |
|---|---|---|
| API 风格 | Options API(选项式) | Composition API(组合式) + <script setup> |
| 状态管理 | Vuex | Pinia(更轻量、更好 TypeScript 支持) |
| 渲染性能 | 旧 Virtual DOM | 重写后的 Virtual DOM,渲染速度大幅提升 |
Vue 3 的核心优势:
- 渐进式:从简单页面到复杂应用,按需引入,无需全栈重构
- Composition API:逻辑复用性更强、代码组织更清晰、天然支持 TypeScript
- 性能:Virtual DOM 重写,配合编译器优化,运行速度和打包体积均显著改善
- 生态成熟:Vue Router 4、Pinia、Vite 组成完整工具链
💡 Vue 2 于 2023 年底正式停止维护,新项目请直接使用 Vue 3。
2. 安装与环境准备
2.1 前提条件
确保已安装 Node.js ≥ 16.0:
bash
node -v # v20.x.x 或更高版本均可
npm -v # npm 8.x+
没有安装?前往 nodejs.org 下载 LTS 版本。
2.2 两种安装方式
方式一:CDN 快速体验(无需构建)
适合写 Demo、验证想法,或者在旧项目中局部引入 Vue 3。创建一个 index.html 即可:
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Vue 3 CDN Demo</title>
</head>
<body>
<div id="app">
<h1>{{ message }}</h1>
<button @click="count++">点击次数:{{ count }}</button>
</div>
<script type="module">
import { createApp, ref } from
'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
createApp({
setup() {
const message = ref('Hello, Vue 3!')
const count = ref(0)
return { message, count }
}
}).mount('#app')
</script>
</body>
</html>
直接在浏览器打开即可运行,无需任何构建工具。
方式二:npm + Vite(推荐工程化方式)
CDN 适合尝鲜,但真实项目需要工程化能力(热更新、模块打包、TypeScript 支持等)。
Vite 是 Vue 官方推荐的构建工具,由 Vue 的作者尤雨溪主导开发,相比 Vue CLI 有显著的速度优势:
npm create vite@latest my-app -- --template vue
cd my-app && npm install && npm run dev
启动后访问 http://localhost:5173/ 即可看到默认项目页面。
3. 创建 Vue 3 项目
3.1 完整步骤
bash
# 1. 创建项目(交互式,会让你选 Vue 或 Vue-ts)
npm create vite@latest my-app -- --template vue
# 2. 进入目录
cd my-app
# 3. 安装依赖
npm install
# 4. 安装 Axios(用于调用后端 API)
npm install axios
# 5. 启动开发服务器
npm run dev
# 输出:Local: http://localhost:5173/
# Network: http://192.168.x.x:5173/
3.2 项目结构一览
my-app/
├── public/ # 静态资源(原样复制到构建产物)
├── src/
│ ├── assets/ # 样式、图片等资源
│ ├── components/ # ✅ 放你的 .vue 组件
│ ├── App.vue # ✅ 根组件,入口
│ └── main.js # ✅ 应用入口文件
├── index.html # HTML 模板
├── vite.config.js # Vite 配置
└── package.json
3.3 安装 Python 后端依赖
后端使用 FastAPI,还需要安装 Python 依赖:
bash
# 建议用虚拟环境
python3 -m venv venv
source venv/bin/activate # macOS/Linux
# venv\Scripts\activate # Windows
pip install fastapi uvicorn
4. Vue 单文件组件基础
Vue 单文件组件(.vue 后缀)将 template(模板)、script(逻辑)、style(样式) 三部分合为一体,是 Vue 项目的标准写法。
4.1 一个最小示例
vue
<!-- src/App.vue -->
<template>
<div class="card">
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<button @click="count++">点击次数:{{ count }}</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
// 响应式状态(Composition API 写法)
const title = ref('Vue3 入门')
const message = ref('你好,世界!')
const count = ref(0)
</script>
<style scoped>
.card {
padding: 1.5rem;
border-radius: 12px;
background: #1e293b;
color: #f1f5f9;
max-width: 400px;
margin: 2rem auto;
}
h1 { color: #42b883; }
button {
background: #42b883;
color: #fff;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 1rem;
}
</style>
4.2 关键语法解释
| 语法 | 说明 |
|---|---|
{``{ title }} |
双大括号插值,渲染变量值 |
@click="count++" |
@click 是 v-on:click 的简写,绑定事件 |
ref() |
创建响应式变量,修改 .value 自动更新视图 |
<style scoped> |
样式仅对当前组件生效,避免污染全局 |
4.3 <script setup> 是什么?
<script setup> 是 Vue 3.2 引入的语法糖,与以下写法完全等价:
javascript
// 编译后等价于:
import { ref } from 'vue'
export default {
setup() {
const title = ref('Vue3 入门')
const message = ref('你好,世界!')
const count = ref(0)
return { title, message, count }
}
}
使用 <script setup> 代码更简洁,是 Vue 3 的推荐写法。
5. 后端:FastAPI + SQLite
5.1 为什么选 FastAPI?
- 性能:接近 Node.js 和 Go,可处理高并发请求
- 自动文档 :运行后访问
http://127.0.0.1:8000/docs,自动生成可交互的 Swagger UI - 类型安全:原生支持 Python 类型提示,配合 Pydantic 自动校验请求数据
- 异步支持 :使用
async def,轻松处理 IO 密集型任务 - 上手极快:Flask 式的简洁语法,Django 式的功能完整
5.2 SQLite:零配置的嵌入式数据库
SQLite 无须独立服务进程,数据存储在一个 .db 文件中,非常适合中小型应用和本地开发:
python
import sqlite3
# 建立连接(文件不存在会自动创建)
conn = sqlite3.connect("data.db")
cursor = conn.cursor()
# 执行 SQL
cursor.execute("""
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT
)
""")
conn.commit()
conn.close()
5.3 最小化后端代码
创建后端文件 backend/main.py:
python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import sqlite3
app = FastAPI(title="Vue3 CRUD API")
DB = "backend/data.db" # SQLite 数据库文件
# ── CORS 配置:允许 Vue 前端跨域访问 ──
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"], # Vite 默认端口
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def get_conn():
"""获取数据库连接的辅助函数"""
return sqlite3.connect(DB)
# ── 启动时初始化数据库 ──
@app.on_event("startup")
def init_db():
with get_conn() as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT
)
""")
conn.commit()
print("✅ 数据库初始化完成")
启动后端:
bash
cd backend
uvicorn main:app --reload --port 8000
# 输出:
# INFO: Uvicorn running on http://127.0.0.1:8000
# ✅ 数据库初始化完成
访问 http://127.0.0.1:8000/docs 可以看到自动生成的交互式 API 文档。
6. FastAPI CRUD 完整代码
下面是与数据库 items 表交互的完整 CRUD 接口。放在 backend/main.py 的 init_db() 之后:
python
from pydantic import BaseModel
# ── Pydantic 模型:定义请求体结构并自动校验类型 ──
class Item(BaseModel):
name: str
description: str | None = None # 可选字段
# ══════════════════════════════════════════════
# 📖 查询全部 GET /items
# ══════════════════════════════════════════════
@app.get("/items")
def get_items():
with get_conn() as conn:
rows = conn.execute("SELECT * FROM items").fetchall()
return [{"id": r[0], "name": r[1], "description": r[2]} for r in rows]
# ══════════════════════════════════════════════
# 📖 查询单条 GET /items/{id}
# ══════════════════════════════════════════════
@app.get("/items/{id}")
def get_item(id: int):
from fastapi import HTTPException
with get_conn() as conn:
r = conn.execute("SELECT * FROM items WHERE id=?", [id]).fetchone()
if not r:
raise HTTPException(status_code=404, detail="Item not found")
return {"id": r[0], "name": r[1], "description": r[2]}
# ══════════════════════════════════════════════
# ➕ 新增 POST /items
# ══════════════════════════════════════════════
@app.post("/items")
def create_item(item: Item):
with get_conn() as conn:
cur = conn.execute(
"INSERT INTO items (name, description) VALUES (?, ?)",
[item.name, item.description]
)
conn.commit()
return {"id": cur.lastrowid, **item.model_dump()}
# ══════════════════════════════════════════════
# ✏️ 修改 PUT /items/{id}
# ══════════════════════════════════════════════
@app.put("/items/{id}")
def update_item(id: int, item: Item):
with get_conn() as conn:
conn.execute(
"UPDATE items SET name=?, description=? WHERE id=?",
[item.name, item.description, id]
)
conn.commit()
return {"id": id, **item.model_dump()}
# ══════════════════════════════════════════════
# 🗑️ 删除 DELETE /items/{id}
# ══════════════════════════════════════════════
@app.delete("/items/{id}")
def delete_item(id: int):
with get_conn() as conn:
conn.execute("DELETE FROM items WHERE id=?", [id])
conn.commit()
return {"ok": True, "deleted": id}
路由方法对照表:
| 方法 | 路径 | 作用 | Postman / curl 示例 |
|---|---|---|---|
GET |
/items |
查询全部记录 | curl http://localhost:8000/items |
GET |
/items/{id} |
查询单条记录 | curl http://localhost:8000/items/1 |
POST |
/items |
新增一条记录 | curl -X POST http://localhost:8000/items -H "Content-Type: application/json" -d '{"name":"测试","description":"描述"}' |
PUT |
/items/{id} |
修改指定记录 | curl -X PUT http://localhost:8000/items/1 -H "Content-Type: application/json" -d '{"name":"修改后","description":"新描述"}' |
DELETE |
/items/{id} |
删除指定记录 | curl -X DELETE http://localhost:8000/items/1 |
7. Vue 调用后端接口
7.1 封装 Axios API 模块
为了代码整洁,把所有 API 调用封装到一个文件 src/api/items.js:
javascript
// src/api/items.js
import axios from 'axios'
// 创建 Axios 实例,配置 baseURL
const api = axios.create({
baseURL: 'http://localhost:8000', // FastAPI 后端地址
headers: { 'Content-Type': 'application/json' }
})
// ── GET /items:查询全部 ──
export const getItems = () => api.get('/items')
// ── GET /items/{id}:查询单条 ──
export const getItem = (id) => api.get(`/items/${id}`)
// ── POST /items:新增 ──
export const createItem = (data) => api.post('/items', data)
// ── PUT /items/{id}:修改 ──
export const updateItem = (id, data) => api.put(`/items/${id}`, data)
// ── DELETE /items/{id}:删除 ──
export const deleteItem = (id) => api.delete(`/items/${id}`)
7.2 在组件中调用
在 Vue 组件中使用 onMounted 钩子在页面加载时获取数据:
vue
<!-- src/App.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import { getItems, createItem, updateItem, deleteItem } from './api/items'
const items = ref([]) // 列表数据
const form = ref({ // 表单数据
name: '',
description: ''
})
const editingId = ref(null) // 当前编辑中的 ID(null 表示新增模式)
// ── 页面加载时获取数据 ──
onMounted(async () => {
try {
const { data } = await getItems()
items.value = data
} catch (err) {
console.error('加载失败', err)
}
})
// ── 新增 ──
const handleCreate = async () => {
const { data } = await createItem(form.value)
items.value.push(data)
form.value = { name: '', description: '' }
}
// ── 点击编辑 ──
const handleEdit = (item) => {
editingId.value = item.id
form.value = { name: item.name, description: item.description }
}
// ── 更新 ──
const handleUpdate = async () => {
const { data } = await updateItem(editingId.value, form.value)
const idx = items.value.findIndex(i => i.id === editingId.value)
if (idx !== -1) items.value[idx] = data // 更新列表中的对应项
editingId.value = null
form.value = { name: '', description: '' }
}
// ── 删除 ──
const handleDelete = async (id) => {
await deleteItem(id)
items.value = items.value.filter(i => i.id !== id) // 从列表中移除
}
</script>
8. 完整 CRUD 页面示例
下面是一个完整的列表管理页面,包含输入表单和表格,代码可直接复制使用。
创建 src/components/ItemList.vue:
vue
<!-- src/components/ItemList.vue -->
<script setup>
import { ref, onMounted } from 'vue'
import { getItems, createItem, updateItem, deleteItem } from '../api/items'
const items = ref([])
const form = ref({ name: '', description: '' })
const editingId = ref(null)
// ── 加载列表 ──
onMounted(async () => {
const { data } = await getItems()
items.value = data
})
// ── 新增 ──
const handleCreate = async () => {
if (!form.value.name.trim()) return
const { data } = await createItem(form.value)
items.value.push(data)
form.value = { name: '', description: '' }
}
// ── 编辑 ──
const handleEdit = (item) => {
editingId.value = item.id
form.value = { name: item.name, description: item.description }
}
// ── 更新 ──
const handleUpdate = async () => {
const { data } = await updateItem(editingId.value, form.value)
const idx = items.value.findIndex(i => i.id === editingId.value)
if (idx !== -1) items.value[idx] = data
editingId.value = null
form.value = { name: '', description: '' }
}
// ── 删除 ──
const handleDelete = async (id) => {
if (!confirm('确认删除?')) return
await deleteItem(id)
items.value = items.value.filter(i => i.id !== id)
}
// ── 取消编辑 ──
const cancelEdit = () => {
editingId.value = null
form.value = { name: '', description: '' }
}
</script>
<template>
<div class="crud-container">
<h2>📋 项目列表管理</h2>
<!-- 输入表单 -->
<div class="form-section">
<input
v-model="form.name"
placeholder="输入名称..."
@keyup.enter="editingId ? handleUpdate() : handleCreate()"
/>
<input
v-model="form.description"
placeholder="输入描述(可选)..."
@keyup.enter="editingId ? handleUpdate() : handleCreate()"
/>
<button v-if="!editingId" @click="handleCreate" class="btn-add">
➕ 新增
</button>
<template v-else>
<button @click="handleUpdate" class="btn-update">✓ 更新</button>
<button @click="cancelEdit" class="btn-cancel">✕ 取消</button>
</template>
</div>
<!-- 列表表格 -->
<table class="items-table">
<thead>
<tr>
<th>ID</th>
<th>名称</th>
<th>描述</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-if="items.length === 0">
<td colspan="4" class="empty-row">暂无数据</td>
</tr>
<tr v-for="item in items" :key="item.id">
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.description || '---' }}</td>
<td>
<button @click="handleEdit(item)" class="btn-edit">✏ 编辑</button>
<button @click="handleDelete(item.id)" class="btn-del">🗑 删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<style scoped>
.crud-container {
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
font-family: system-ui, sans-serif;
}
h2 { color: #42b883; margin-bottom: 1rem; }
.form-section {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
flex-wrap: wrap;
}
input {
padding: 0.5rem 0.75rem;
border: 1px solid #334155;
border-radius: 6px;
background: #1e293b;
color: #f1f5f9;
font-size: 0.95rem;
flex: 1;
min-width: 160px;
}
input::placeholder { color: #64748b; }
input:focus { outline: 2px solid #42b883; border-color: transparent; }
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: opacity 0.2s;
}
button:hover { opacity: 0.85; }
.btn-add { background: #22c55e; color: #fff; }
.btn-update { background: #3b82f6; color: #fff; }
.btn-cancel { background: #475569; color: #fff; }
.btn-edit { background: #f59e0b; color: #fff; margin-right: 0.4rem; }
.btn-del { background: #ef4444; color: #fff; }
.items-table {
width: 100%;
border-collapse: collapse;
background: #1e293b;
border-radius: 8px;
overflow: hidden;
}
th {
background: #0f172a;
color: #94a3b8;
font-weight: 600;
text-align: left;
padding: 0.75rem 1rem;
font-size: 0.85rem;
letter-spacing: 0.05em;
}
td {
padding: 0.75rem 1rem;
border-top: 1px solid #334155;
color: #f1f5f9;
font-size: 0.9rem;
}
tr:hover td { background: #1e293b; }
.empty-row { text-align: center; color: #64748b; padding: 2rem; }
</style>
然后在 App.vue 中引入使用:
vue
<!-- src/App.vue -->
<script setup>
import ItemList from './components/ItemList.vue'
</script>
<template>
<main>
<ItemList />
</main>
</template>
9. 下一步建议
掌握以上内容后,可以沿着以下方向继续深入:
路由与页面导航
安装 Vue Router,实现多页面切换:
bash
npm install vue-router
状态管理
使用 Pinia 在组件间共享复杂状态:
bash
npm install pinia
后端进阶
- 用 SQLAlchemy 或 Peewee ORM 替代手写 SQL
- 添加 JWT 认证 保护接口
- 使用 PostgreSQL 替代 SQLite 支持生产部署
部署上线
- 前端:运行
npm run build,用 Nginx 做反向代理 - 后端:用 Docker 容器化,或部署到 Railway / Render / 阿里云 ECS
学习资源
完整项目结构
project/
├── backend/
│ ├── main.py # FastAPI 后端(CRUD 接口)
│ └── data.db # SQLite 数据库(自动生成)
└── my-app/ # Vue 3 前端
├── src/
│ ├── api/
│ │ └── items.js # Axios API 封装
│ ├── components/
│ │ └── ItemList.vue # 完整 CRUD 页面
│ └── App.vue
└── vite.config.js
启动方式:
bash
# 终端 1:后端
cd backend && uvicorn main:app --reload --port 8000
# 终端 2:前端
cd my-app && npm run dev
打开 http://localhost:5173 即可看到完整的增删改查应用。
祝编码愉快!