引言
在当今快速发展的Web开发领域,前后端分离架构已成为主流趋势。本文将带您从零开始构建一个功能完整的任务管理单页应用(SPA),结合Vue.js前端框架与Django REST Framework后端API服务。通过这个实战项目,您不仅能掌握现代Web开发的核心技术栈,还能学习到企业级应用开发的最佳实践。
一、项目概述与技术选型
1.1 项目功能需求
我们将开发一个具备以下核心功能的任务管理系统:
-
用户认证:注册、登录、登出
-
任务管理:创建、查看、更新、删除任务
-
任务分类:按状态(待办、进行中、已完成)筛选
-
搜索功能:按标题或内容搜索任务
-
响应式设计:适配不同设备屏幕
1.2 技术栈选择
技术 | 作用 | 优势分析 |
---|---|---|
Vue 3 | 前端框架 | 响应式、组合式API、良好生态 |
Vue Router | 前端路由管理 | SPA路由控制、导航守卫 |
Pinia | 状态管理 | 轻量级、TypeScript支持 |
Axios | HTTP客户端 | Promise API、拦截器支持 |
Django | 后端框架 | ORM强大、Admin后台、安全性高 |
DRF | REST API构建 | 序列化、认证、权限、视图集 |
JWT | 认证机制 | 无状态、跨域支持、安全性好 |
二、后端开发:Django REST Framework实现
2.1 项目初始化
bash
# 创建Django项目
django-admin startproject taskmanager_backend
cd taskmanager_backend
# 创建核心应用
python manage.py startapp tasks
python manage.py startapp users
# 安装必要依赖
pip install djangorestframework django-cors-headers pyjwt
2.2 数据模型设计
tasks/models.py
:
python
from django.db import models
from django.contrib.auth import get_user_model
User = get_user_model()
class Task(models.Model):
STATUS_CHOICES = [
('TODO', '待办'),
('IN_PROGRESS', '进行中'),
('DONE', '已完成'),
]
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
description = models.TextField(blank=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='TODO')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
due_date = models.DateTimeField(null=True, blank=True)
def __str__(self):
return self.title
2.3 序列化器实现
tasks/serializers.py
:
python
from rest_framework import serializers
from .models import Task
from users.serializers import UserSerializer
class TaskSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Task
fields = ['id', 'user', 'title', 'description', 'status',
'created_at', 'updated_at', 'due_date']
read_only_fields = ['id', 'user', 'created_at', 'updated_at']
2.4 视图集与路由配置
tasks/views.py
:
python
from rest_framework import viewsets, permissions
from .models import Task
from .serializers import TaskSerializer
from .permissions import IsOwnerOrReadOnly
class TaskViewSet(viewsets.ModelViewSet):
serializer_class = TaskSerializer
permission_classes = [permissions.IsAuthenticated, IsOwnerOrReadOnly]
def get_queryset(self):
# 只返回当前用户的任务
return Task.objects.filter(user=self.request.user)
def perform_create(self, serializer):
# 创建时自动关联当前用户
serializer.save(user=self.request.user)
tasks/permissions.py
:
python
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
自定义权限:只允许任务的所有者编辑
"""
def has_object_permission(self, request, view, obj):
# 安全方法(GET, HEAD, OPTIONS)允许所有请求
if request.method in permissions.SAFE_METHODS:
return True
# 写入权限仅限任务所有者
return obj.user == request.user
2.5 JWT认证实现
users/views.py
:
python
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import authenticate
from .serializers import UserSerializer
class RegisterView(APIView):
def post(self, request):
serializer = UserSerializer(data=request.data)
if serializer.is_valid():
user = serializer.save()
refresh = RefreshToken.for_user(user)
return Response({
'user': serializer.data,
'refresh': str(refresh),
'access': str(refresh.access_token),
}, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class LoginView(APIView):
def post(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = authenticate(username=username, password=password)
if user is None:
return Response(
{'error': 'Invalid credentials'},
status=status.HTTP_401_UNAUTHORIZED
)
refresh = RefreshToken.for_user(user)
return Response({
'refresh': str(refresh),
'access': str(refresh.access_token),
})
三、前端开发:Vue.js实现
3.1 项目初始化
bash
# 使用Vite创建Vue项目
npm create vite@latest taskmanager_frontend --template vue
cd taskmanager_frontend
# 安装必要依赖
npm install vue-router@4 pinia axios vue-axios
npm install @vueuse/core lodash-es
npm install --save-dev sass
3.2 项目结构设计
bash
src/
├── api/ # API请求封装
├── assets/ # 静态资源
├── components/ # 公共组件
│ ├── TaskCard.vue
│ ├── TaskForm.vue
│ └── ...
├── composables/ # 组合式函数
├── router/ # 路由配置
├── stores/ # Pinia状态管理
├── styles/ # 全局样式
├── utils/ # 工具函数
├── views/ # 页面组件
│ ├── Auth/
│ ├── Dashboard/
│ └── ...
├── App.vue
└── main.js
3.3 Pinia状态管理
stores/authStore.js
:
javascript
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { login, register, logout } from '@/api/auth'
import router from '@/router'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(localStorage.getItem('token'))
const isAuthenticated = ref(false)
const setAuth = (userData, authToken) => {
user.value = userData
token.value = authToken
isAuthenticated.value = true
localStorage.setItem('token', authToken)
}
const clearAuth = () => {
user.value = null
token.value = null
isAuthenticated.value = false
localStorage.removeItem('token')
}
const handleLogin = async (credentials) => {
try {
const response = await login(credentials)
setAuth(response.user, response.access)
router.push('/dashboard')
} catch (error) {
clearAuth()
throw error
}
}
const handleRegister = async (userData) => {
try {
const response = await register(userData)
setAuth(response.user, response.access)
router.push('/dashboard')
} catch (error) {
clearAuth()
throw error
}
}
const handleLogout = async () => {
await logout()
clearAuth()
router.push('/login')
}
return {
user,
token,
isAuthenticated,
handleLogin,
handleRegister,
handleLogout
}
})
3.4 API服务封装
api/tasks.js
:
javascript
import axios from 'axios'
import { useAuthStore } from '@/stores/authStore'
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})
// 请求拦截器
apiClient.interceptors.request.use((config) => {
const authStore = useAuthStore()
if (authStore.token) {
config.headers.Authorization = `Bearer ${authStore.token}`
}
return config
}, (error) => {
return Promise.reject(error)
})
// 响应拦截器
apiClient.interceptors.response.use((response) => {
return response
}, (error) => {
if (error.response?.status === 401) {
const authStore = useAuthStore()
authStore.handleLogout()
}
return Promise.reject(error)
})
export default {
getTasks(params = {}) {
return apiClient.get('/tasks/', { params })
},
getTask(id) {
return apiClient.get(`/tasks/${id}/`)
},
createTask(taskData) {
return apiClient.post('/tasks/', taskData)
},
updateTask(id, taskData) {
return apiClient.patch(`/tasks/${id}/`, taskData)
},
deleteTask(id) {
return apiClient.delete(`/tasks/${id}/`)
}
}
3.5 任务列表组件实现
components/TaskList.vue
:
html
<script setup>
import { computed, ref } from 'vue'
import { useTaskStore } from '@/stores/taskStore'
import TaskCard from './TaskCard.vue'
import TaskForm from './TaskForm.vue'
const taskStore = useTaskStore()
const showForm = ref(false)
const editingTask = ref(null)
const tasks = computed(() => taskStore.tasks)
const filteredTasks = computed(() => {
return tasks.value.filter(task => {
// 根据状态筛选逻辑
return true
})
})
const handleEdit = (task) => {
editingTask.value = task
showForm.value = true
}
const handleSubmit = async (taskData) => {
if (editingTask.value) {
await taskStore.updateTask(editingTask.value.id, taskData)
} else {
await taskStore.createTask(taskData)
}
showForm.value = false
editingTask.value = null
}
</script>
<template>
<div class="task-list">
<button @click="showForm = true" class="add-button">
添加任务
</button>
<TaskForm
v-if="showForm"
:initial-data="editingTask"
@submit="handleSubmit"
@cancel="showForm = false"
/>
<div v-if="filteredTasks.length" class="tasks-grid">
<TaskCard
v-for="task in filteredTasks"
:key="task.id"
:task="task"
@edit="handleEdit"
/>
</div>
<p v-else class="empty-message">
暂无任务,点击上方按钮添加
</p>
</div>
</template>
<style scoped>
.task-list {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.add-button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-bottom: 20px;
}
.tasks-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.empty-message {
text-align: center;
color: #666;
font-size: 1.2rem;
}
</style>
四、前后端联调与部署
4.1 跨域问题解决
taskmanager_backend/settings.py
:
python
INSTALLED_APPS = [
...
'corsheaders',
]
MIDDLEWARE = [
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
]
# 允许所有来源(生产环境应配置具体域名)
CORS_ALLOW_ALL_ORIGINS = True
# 或指定允许的域名
CORS_ALLOWED_ORIGINS = [
"http://localhost:5173",
"https://your-production-domain.com"
]
4.2 环境变量配置
.env.development
:
bash
VITE_API_BASE_URL=http://localhost:8000/api
vue.config.js
:
javascript
import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd())
return {
plugins: [vue()],
server: {
proxy: {
'/api': {
target: env.VITE_API_BASE_URL,
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
}
})
4.3 生产环境部署
后端部署(Nginx + Gunicorn):
安装Gunicorn:
bash
pip install gunicorn
创建Gunicorn服务:
bash
gunicorn --workers 3 --bind unix:taskmanager.sock taskmanager_backend.wsgi:application
Nginx配置:
html
server {
listen 80;
server_name api.yourdomain.com;
location / {
include proxy_params;
proxy_pass http://unix:/path/to/taskmanager.sock;
}
location /static/ {
alias /path/to/your/project/staticfiles/;
}
}
前端部署:
构建生产版本:
bash
npm run build
Nginx配置:
html
server {
listen 80;
server_name yourdomain.com;
root /path/to/taskmanager_frontend/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://api.yourdomain.com;
}
}
五、项目优化与扩展
5.1 性能优化
前端优化:
- 组件懒加载
javascript
const Dashboard = () => import('@/views/Dashboard.vue')
- 路由懒加载
javascript
{
path: '/dashboard',
component: () => import('@/views/Dashboard.vue')
}
- 使用
v-memo
优化大型列表渲染
后端优化:
-
数据库查询优化(select_related/prefetch_related)
-
分页支持
-
缓存常用数据
5.2 功能扩展
-
实时更新:
-
使用WebSocket实现任务实时同步
-
集成Django Channels
-
-
文件上传:
-
实现任务附件功能
-
使用Django的FileField和Vue的文件上传组件
-
-
数据可视化:
-
使用ECharts或Chart.js展示任务统计图表
-
实现任务完成情况的时间线视图
-
5.3 测试策略
-
前端测试:
-
单元测试:Vitest + Vue Test Utils
-
E2E测试:Cypress
-
-
后端测试:
-
单元测试:Django TestCase
-
API测试:DRF APITestCase
-
六、总结与展望
通过本项目的实践,我们完成了:
-
基于Django REST Framework构建了功能完善的RESTful API后端
-
使用Vue 3组合式API开发了响应式的前端SPA应用
-
实现了JWT认证的安全机制
-
掌握了前后端分离架构的开发流程
-
学习了项目部署的基本方法
项目亮点:
-
采用现代化技术栈,符合当前行业趋势
-
完善的认证与权限控制
-
响应式设计,适配多种设备
-
清晰的代码结构与模块化设计
未来改进方向:
-
增加团队协作功能,支持多人任务分配
-
实现任务提醒和通知系统
-
集成第三方登录(Google、GitHub等)
-
开发移动端应用(React Native或Flutter)
希望这篇实战教程能帮助您掌握Vue+Django REST Framework全栈开发技能!如果您在实践过程中遇到任何问题,欢迎在评论区留言讨论。