实战教程:基于Vue.js与Django REST Framework的任务管理SPA开发全流程

引言

在当今快速发展的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 功能扩展

  1. 实时更新:

    • 使用WebSocket实现任务实时同步

    • 集成Django Channels

  2. 文件上传:

    • 实现任务附件功能

    • 使用Django的FileField和Vue的文件上传组件

  3. 数据可视化:

    • 使用ECharts或Chart.js展示任务统计图表

    • 实现任务完成情况的时间线视图

5.3 测试策略

  1. 前端测试:

    • 单元测试:Vitest + Vue Test Utils

    • E2E测试:Cypress

  2. 后端测试:

    • 单元测试:Django TestCase

    • API测试:DRF APITestCase

六、总结与展望

通过本项目的实践,我们完成了:

  1. 基于Django REST Framework构建了功能完善的RESTful API后端

  2. 使用Vue 3组合式API开发了响应式的前端SPA应用

  3. 实现了JWT认证的安全机制

  4. 掌握了前后端分离架构的开发流程

  5. 学习了项目部署的基本方法

项目亮点:

  • 采用现代化技术栈,符合当前行业趋势

  • 完善的认证与权限控制

  • 响应式设计,适配多种设备

  • 清晰的代码结构与模块化设计

未来改进方向:

  1. 增加团队协作功能,支持多人任务分配

  2. 实现任务提醒和通知系统

  3. 集成第三方登录(Google、GitHub等)

  4. 开发移动端应用(React Native或Flutter)

希望这篇实战教程能帮助您掌握Vue+Django REST Framework全栈开发技能!如果您在实践过程中遇到任何问题,欢迎在评论区留言讨论。

相关推荐
崔庆才丨静觅11 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606111 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了11 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅11 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅12 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅12 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment12 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅13 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊13 小时前
jwt介绍
前端
爱敲代码的小鱼13 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax