django與vue3的對接流程詳解(下) - 教程
- 在前一篇文章中,我們已經掌握了 Django5 表單的構建邏輯(Form/ModelForm)和前后端數據傳遞方案(axios 發送 + Django 接收)。但完整的前后端交互鏈路,還需要“數據庫操作”和“前端數據渲染”兩個關鍵環節——Django 需要將接收的前端數據存入數據庫,并在前端需要時查詢數據并以 JSON 格式響應;Vue3 則需要接收響應數據,渲染到頁面上,形成“輸入-處理-存儲-展示”的閉環。本文作為系列第二篇,將聚焦這兩大環節,并通過“產品管理系統”綜合案例,完整演示前后端協同的實現流程。
一、Django5 操作數據庫并響應數據:完成后端數據流轉
Django 與數據庫的交互依賴于 ORM(對象關系映射)機制——它允許開發者用 Python 代碼替代 SQL 語句,輕松實現數據的查詢、新增、更新、刪除,且無需關注底層數據庫類型(如 MySQL、PostgreSQL)的差異。同時,Django 提供 JsonResponse 類,可將查詢結果轉為 JSON 格式,供 Vue3 接收和渲染。
1.1 Django ORM:簡化數據庫查詢操作
ORM 的核心價值是“屏蔽數據庫差異,降低操作復雜度”。針對表單交互場景,最常用的是“數據查詢”操作——需根據前端需求,從數據庫中篩選、排序、分頁數據,再返回給前端。以下是幾種高頻查詢場景及實現方式。
(1)基礎查詢:獲取全部/單條/條件數據
基礎查詢是最常用的場景,涵蓋“獲取所有數據”“按條件篩選”“獲取單條數據”等,通過 ORM 提供的 all()、filter()、get() 等方法即可實現。
示例:文章模型基礎查詢
# views.py
from django.utils import timezone
from datetime import timedelta
from .models import Article
# 1. 獲取所有數據(返回查詢集,支持鏈式調用)
all_articles = Article.objects.all()
# 2. 條件查詢:篩選符合條件的數據(返回查詢集)
# 場景1:獲取標題包含“Django”的文章(模糊查詢,用__contains)
django_articles = Article.objects.filter(title__contains='Django')
# 場景2:獲取近7天發布的文章(時間條件,用__gte表示“大于等于”)
recent_7_days = timezone.now() - timedelta(days=7)
recent_articles = Article.objects.filter(pub_date__gte=recent_7_days)
# 場景3:獲取標題不為空且內容長度大于100的文章(多條件組合)
valid_articles = Article.objects.filter(
title__isnull=False, # 標題不為空
content__length__gt=100 # 內容長度大于100(__gt表示“大于”)
)
# 3. 獲取單條數據(返回模型實例,若不存在則拋出DoesNotExist異常)
# 場景:根據文章ID獲取詳情(常用于“編輯/刪除”功能)
try:
article = Article.objects.get(id=1) # id=1的文章
except Article.DoesNotExist:
# 處理“數據不存在”的情況(如返回錯誤提示)
print("該文章不存在")
(2)高級查詢:排序與分頁
當數據量較大時,需對查詢結果進行“排序”和“分頁”,提升前端渲染效率和用戶體驗。ORM 提供 order_by() 方法實現排序,Django 內置的 Paginator 類實現分頁。
示例:排序與分頁查詢
# views.py
from django.core.paginator import Paginator
from .models import Article
# 1. 排序查詢:按指定字段排序(正序/倒序)
# 場景1:按發布時間正序(從舊到新):order_by('pub_date')
# 場景2:按發布時間倒序(從新到舊):字段前加負號(-)
ordered_articles = Article.objects.all().order_by('-pub_date')
# 場景3:多字段排序:先按發布時間倒序,再按標題正序
multi_order_articles = Article.objects.all().order_by('-pub_date', 'title')
# 2. 分頁查詢:拆分數據為多頁,減少單次數據傳輸量
# 步驟1:創建分頁器實例(參數1:查詢集,參數2:每頁數據量)
paginator = Paginator(ordered_articles, 10) # 每頁10條文章
# 步驟2:獲取指定頁碼的數據(如第1頁、第2頁)
page_number = 1 # 前端可通過URL參數傳遞頁碼(如?page=1)
page_obj = paginator.page(page_number)
# 步驟3:提取當前頁的具體數據(供后續響應給前端)
current_page_articles = page_obj.object_list # 當前頁的文章列表
# 步驟4:分頁輔助信息(返回給前端,用于渲染分頁控件)
pagination_info = {
'total_count': paginator.count, # 總數據量
'total_pages': paginator.num_pages, # 總頁數
'current_page': page_number, # 當前頁碼
'has_next': page_obj.has_next(), # 是否有下一頁
'has_previous': page_obj.has_previous() # 是否有上一頁
}
(3)字段篩選:僅查詢所需字段
默認情況下,Article.objects.all() 會查詢模型的所有字段(如 id、title、content、pub_date),但前端可能只需部分字段(如列表頁只需 id、title、pub_date)。此時可通過 values() 方法篩選字段,減少數據傳輸量。
示例:字段篩選查詢
# 僅查詢id、title、pub_date三個字段(返回字典列表)
articles_simple = Article.objects.all().values('id', 'title', 'pub_date')
# 若需排序+字段篩選:鏈式調用即可
articles_sorted_simple = Article.objects.all()\
.order_by('-pub_date')\
.values('id', 'title', 'pub_date')
1.2 Django 以 JSON 格式響應數據:適配前端渲染需求
查詢到數據后,Django 需要將其轉為 JSON 格式返回給 Vue3。核心工具是 JsonResponse 類,但需注意:Django 的查詢集(如 Article.objects.all())是“可迭代對象”,無法直接被 JSON 序列化,需先通過 list() 方法轉為列表;若包含 Decimal(如價格)、datetime(如時間)等特殊類型,需先轉為字符串或浮點數,避免序列化錯誤。
(1)基礎 JSON 響應:自定義視圖實現
適用于簡單場景,手動處理數據序列化和響應格式。
示例:返回文章列表 JSON 數據
# views.py
from django.http import JsonResponse
from .models import Article
def get_articles_api(request):
# 1. 查詢數據(字段篩選+排序)
articles = Article.objects.all()\
.order_by('-pub_date')\
.values('id', 'title', 'pub_date') # 僅查詢所需字段
# 2. 處理特殊類型:將datetime轉為字符串(便于JSON序列化)
# 方法:遍歷查詢結果,格式化pub_date
articles_list = []
for item in articles:
# 將datetime對象轉為“YYYY-MM-DD HH:MM:SS”格式字符串
item['pub_date'] = item['pub_date'].strftime('%Y-%m-%d %H:%M:%S')
articles_list.append(item)
# 3. 返回JSON響應(包含狀態、消息、數據)
return JsonResponse({
'status': 'success', # 狀態:success/error
'message': '文章列表獲取成功', # 提示消息
'data': articles_list, # 核心數據(文章列表)
'count': len(articles_list) # 數據總量(輔助前端顯示)
})
(2)帶分頁的 JSON 響應:適配大量數據場景
當數據量較大時,需在響應中包含分頁信息,供前端渲染分頁控件(如下一頁、上一頁按鈕)。
示例:返回帶分頁的文章列表
# views.py
from django.http import JsonResponse
from django.core.paginator import Paginator
from .models import Article
def get_articles_with_pagination_api(request):
# 1. 獲取前端傳遞的頁碼(默認第1頁)
page_number = request.GET.get('page', 1) # 從URL參數?page=獲取
try:
page_number = int(page_number) # 轉為整數(避免非數字參數)
except ValueError:
page_number = 1 # 若參數非法,默認第1頁
# 2. 查詢并分頁數據
articles_queryset = Article.objects.all()\
.order_by('-pub_date')\
.values('id', 'title', 'pub_date')
paginator = Paginator(articles_queryset, 10) # 每頁10條
page_obj = paginator.get_page(page_number) # 自動處理“頁碼超出范圍”(返回最后一頁)
# 3. 處理數據格式(格式化時間+提取當前頁數據)
current_page_articles = []
for item in page_obj.object_list:
item['pub_date'] = item['pub_date'].strftime('%Y-%m-%d %H:%M:%S')
current_page_articles.append(item)
# 4. 構造分頁信息
pagination = {
'total_count': paginator.count,
'total_pages': paginator.num_pages,
'current_page': page_number,
'has_next': page_obj.has_next(),
'has_previous': page_obj.has_previous(),
'next_page': page_obj.next_page_number() if page_obj.has_next() else None,
'previous_page': page_obj.previous_page_number() if page_obj.has_previous() else None
}
# 5. 返回JSON響應(包含數據和分頁信息)
return JsonResponse({
'status': 'success',
'message': '帶分頁的文章列表獲取成功',
'data': current_page_articles,
'pagination': pagination # 分頁信息
})
(3)DRF 自動 JSON 響應:復雜場景優選
若使用 Django REST framework(DRF),無需手動處理數據序列化和分頁——DRF 會自動將查詢集轉為 JSON 格式,并默認支持分頁,大幅減少代碼量。
示例:DRF 實現文章列表響應
# 1. 序列化器(復用前一篇的ArticleSerializer)
# serializers.py
from rest_framework import serializers
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['id', 'title', 'content', 'pub_date']
read_only_fields = ['pub_date']
# 2. 視圖:使用DRF的ListAPIView(專門用于返回列表數據)
# views.py
from rest_framework import generics
from .models import Article
from .serializers import ArticleSerializer
class ArticleListView(generics.ListAPIView):
# 數據源:按發布時間倒序
queryset = Article.objects.all().order_by('-pub_date')
# 關聯的序列化器(自動處理數據轉換)
serializer_class = ArticleSerializer
# 配置分頁(每頁10條)
pagination_class = generics.pagination.PageNumberPagination
pagination_class.page_size = 10 # 每頁數據量
# 3. URL配置(urls.py)
from django.urls import path
from .views import ArticleListView
urlpatterns = [
# 文章列表接口:GET /api/articles/
path('api/articles/', ArticleListView.as_view(), name='article-list'),
]
DRF 自動返回的 JSON 格式
訪問 GET /api/articles/,DRF 會自動返回包含分頁信息的 JSON 數據,格式如下:
{
"count": 120, // 總數據量
"next": "http://example.com/api/articles/?page=2", // 下一頁URL
"previous": null, // 上一頁URL(第一頁為null)
"results": [ // 當前頁數據列表
{
"id": 120,
"title": "Django5 ORM 高級用法",
"content": "本文講解Django5 ORM的復雜查詢...",
"pub_date": "2024-10-01T14:30:00Z" // 時間自動轉為ISO格式
},
// ... 其他9條文章數據
]
}
Vue3 可直接使用 response.data.results 獲取當前頁數據,response.data.next/response.data.previous 處理分頁跳轉,無需手動解析分頁邏輯。
1.3 Vue3 接收并渲染數據:完成前端展示閉環
Vue3 接收 Django 響應的 JSON 數據后,需通過“響應式數據存儲-條件渲染-列表渲染”的流程,將數據展示到頁面上。同時,為提升用戶體驗,需添加“加載狀態”(避免用戶等待時無反饋)和“錯誤提示”(處理請求失敗場景)。
(1)基礎數據渲染:文章列表展示
核心邏輯是:在組件掛載時(onMounted)調用 axios 請求 Django 接口,將返回的數據存入響應式變量(articles),再通過 v-for 循環渲染列表。
示例:Vue3 文章列表渲染
文章列表
加載中...
? {{ error }}
{{ article.title }}
暫無文章數據,請先發布文章
<script setup>
// 1. 導入所需工具
import { ref, onMounted } from 'vue';
import axios from 'axios';
import { useRouter } from 'vue-router'; // 用于頁面跳轉(查看詳情)
// 2. 初始化響應式數據
const articles = ref([]); // 存儲文章列表數據
const loading = ref(false); // 存儲加載狀態(true:加載中,false:加載完成)
const error = ref(''); // 存儲錯誤信息(請求失敗時賦值)
const router = useRouter(); // 初始化路由實例
// 3. 定義“獲取文章列表”的異步函數
const fetchArticles = async () => {
loading.value = true; // 開始加載:顯示加載狀態
error.value = ''; // 清空之前的錯誤信息
try {
// 發送GET請求到Django接口(若有分頁,可添加?page=1參數)
const response = await axios.get('/api/articles/');
// 處理響應數據:
// - 若為DRF接口:數據在response.data.results中
// - 若為自定義視圖:數據在response.data.data中
const articleData = response.data.results || response.data.data;
articles.value = articleData; // 將數據存入響應式變量
} catch (err) {
// 請求失敗:捕獲錯誤并賦值給error
error.value = '獲取文章列表失敗,請刷新頁面重試';
console.error('請求錯誤詳情:', err); // 控制臺打印錯誤,便于調試
} finally {
// 無論成功/失敗,都結束加載:隱藏加載狀態
loading.value = false;
}
};
// 4. 定義“時間格式化”函數(處理ISO格式時間)
const formatDate = (dateString) => {
// 若為DRF返回的ISO時間(如2024-10-01T14:30:00Z),需轉為本地時間
const date = new Date(dateString);
// 格式化為“YYYY-MM-DD HH:MM”(適配中文環境)
return date.toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
});
};
// 5. 定義“跳轉至文章詳情”函數
const goToDetail = (articleId) => {
// 跳轉到詳情頁(路由需提前定義,如/articles/:id)
router.push(`/articles/${articleId}`);
};
// 6. 組件掛載時自動調用“獲取文章列表”函數
onMounted(() => {
fetchArticles();
});
</script>
(2)帶分頁的 data 渲染:適配大量數據
若 Django 接口返回分頁信息,Vue3 需在頁面上渲染分頁控件(如頁碼按鈕、上一頁/下一頁按鈕),允許用戶切換頁碼。
示例:Vue3 帶分頁的文章列表
{{ pagination.current_page }} / {{ pagination.total_pages }} 頁(共 {{ pagination.total_count }} 條)
<script setup>
// 1. 新增響應式變量:存儲分頁信息
const pagination = ref(null); // 存儲分頁信息(如total_count、current_page)
// 2. 修改fetchArticles函數:接收并存儲分頁信息
const fetchArticles = async (page = 1) => { // 新增page參數,默認第1頁
loading.value = true;
error.value = '';
try {
// 發送請求時攜帶頁碼參數(?page=page)
const response = await axios.get(`/api/articles/?page=${page}`);
// 存儲文章數據(同基礎版)
articles.value = response.data.results || response.data.data;
// 存儲分頁信息:
// - 若為DRF接口:分頁信息在response.data中(count、next、previous)
// - 若為自定義視圖:分頁信息在response.data.pagination中
if (response.data.results) {
// DRF分頁信息適配
pagination.value = {
total_count: response.data.count,
total_pages: Math.ceil(response.data.count / 10), // 每頁10條,計算總頁數
current_page: page,
has_next: !!response.data.next, // next不為null則有下一頁
has_previous: !!response.data.previous // previous不為null則有上一頁
};
} else {
// 自定義視圖分頁信息適配
pagination.value = response.data.pagination;
}
} catch (err) {
error.value = '獲取文章列表失敗,請刷新頁面重試';
console.error(err);
} finally {
loading.value = false;
}
};
// 3. 定義“切換頁碼”函數
const changePage = (targetPage) => {
// 邊界校驗:避免頁碼小于1或大于總頁數
if (targetPage < 1 || targetPage > pagination.value.total_pages) {
return;
}
// 重新請求目標頁碼的數據
fetchArticles(targetPage);
};
// 4. 定義“計算可見頁碼”函數(避免頁碼過多,僅顯示當前頁前后2頁)
const visiblePages = ref([]);
// 監聽pagination變化,動態計算可見頁碼
watch(pagination, (newPagination) => {
if (!newPagination) return;
const { current_page, total_pages } = newPagination;
const pages = [];
// 計算起始頁碼(當前頁-2,最小為1)
const startPage = Math.max(1, current_page - 2);
// 計算結束頁碼(當前頁+2,最大為總頁數)
const endPage = Math.min(total_pages, current_page + 2);
// 生成可見頁碼列表
for (let i = startPage; i <= endPage; i++) {
pages.push(i);
}
visiblePages.value = pages;
}, { immediate: true }); // immediate: true:初始時立即執行
// 5. 組件掛載時調用:默認獲取第1頁數據
onMounted(() => {
fetchArticles();
});
</script>
二、綜合案例:產品管理系統(前后端完整實現)
為了將前面所學的知識點串聯起來,我們以“產品管理系統”為例,實現一個完整的前后端交互功能:Vue3 前端提供“添加產品”表單和“產品列表”展示;Django5 后端接收前端數據,存儲到 MySQL 數據庫,并在前端需要時查詢數據并響應。
2.1 項目前提準備
(1)Django 項目配置(MySQL)
首先確保 Django 已配置 MySQL 數據庫(若未配置,需修改 settings.py):
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 數據庫引擎
'NAME': 'product_db', # 數據庫名稱(需提前在MySQL中創建)
'USER': 'root', # MySQL用戶名
'PASSWORD': 'your_password', # MySQL密碼
'HOST': 'localhost', # 數據庫主機(本地為localhost)
'PORT': '3306', # 數據庫端口(默認3306)
'OPTIONS': {
'charset': 'utf8mb4' # 字符集(支持emoji等特殊字符)
}
}
}
(2)Vue3 項目準備
確保 Vue3 項目已安裝 axios(用于 HTTP 請求)和 vue-router(用于頁面跳轉):
# 安裝axios
npm install axios
# 安裝vue-router(若需頁面跳轉)
npm install vue-router@4
2.2 Django 后端實現(產品管理接口)
(1)定義產品模型(models.py)
# models.py
from django.db import models
class Product(models.Model):
"""產品模型:存儲產品名稱、價格、描述、創建時間"""
name = models.CharField(max_length=200, verbose_name='產品名稱') # 產品名稱(最大200字符)
price = models.DecimalField(
max_digits=10,
decimal_places=2,
verbose_name='產品價格'
) # 產品價格(最大10位數字,保留2位小數)
description = models.TextField(blank=True, null=True, verbose_name='產品描述') # 產品描述(可選)
created_at = models.DateTimeField(auto_now_add=True, verbose_name='創建時間') # 自動填充創建時間
class Meta:
verbose_name = '產品' # 后臺管理顯示的單數名稱
verbose_name_plural = '產品' # 后臺管理顯示的復數名稱
ordering = ['-created_at'] # 默認排序:按創建時間倒序
def __str__(self):
return self.name # 后臺管理中顯示產品名稱
定義完成后,執行數據庫遷移命令,創建 product 表:
# 生成遷移文件
python manage.py makemigrations
# 執行遷移(創建表)
python manage.py migrate
(3)定義視圖函數(views.py)
實現兩個核心接口:create_product(添加產品,POST 請求)和 get_products(獲取產品列表,GET 請求,支持分頁)。
# views.py
import json
from django.http import JsonResponse
from django.core.paginator import Paginator
from .models import Product
def create_product(request):
"""添加產品接口:接收Vue3發送的JSON數據,存儲到數據庫"""
if request.method == 'POST':
try:
# 1. 解析JSON數據
data = json.loads(request.body.decode('utf-8'))
# 2. 提取并校驗字段
name = data.get('name', '').strip()
price = data.get('price', 0)
description = data.get('description', '').strip()
# 校驗邏輯:名稱非空,價格為正數
errors = {}
if not name:
errors['name'] = '產品名稱不能為空'
if not isinstance(price, (int, float)) or price <= 0:
errors['price'] = '產品價格必須為正數'
# 若有錯誤,返回錯誤信息
if errors:
return JsonResponse({
'status': 'error',
'message': '數據校驗失敗',
'errors': errors
}, status=400)
# 3. 存儲數據到數據庫(DecimalField需轉為float)
product = Product.objects.create(
name=name,
price=float(price),
description=description
)
# 4. 返回成功響應
return JsonResponse({
'status': 'success',
'message': '產品添加成功',
'data': {
'id': product.id,
'name': product.name,
'price': float(product.price), # Decimal轉float,避免JSON序列化錯誤
'created_at': product.created_at.strftime('%Y-%m-%d %H:%M:%S')
}
})
# 處理JSON格式錯誤
except json.JSONDecodeError:
return JsonResponse({
'status': 'error',
'message': '數據格式錯誤,請發送合法的JSON'
}, status=400)
# 非POST請求響應
return JsonResponse({
'status': 'error',
'message': '僅支持POST請求'
}, status=405)
def get_products(request):
"""獲取產品列表接口:支持分頁,返回JSON數據"""
if request.method == 'GET':
# 1. 獲取頁碼(默認第1頁)
page_number = request.GET.get('page', 1)
try:
page_number = int(page_number)
except ValueError:
page_number = 1
# 2. 查詢產品數據(字段篩選:僅返回所需字段)
products_queryset = Product.objects.all().values(
'id', 'name', 'price', 'created_at'
)
# 3. 分頁處理(每頁8條數據)
paginator = Paginator(products_queryset, 8)
page_obj = paginator.get_page(page_number)
# 4. 處理數據格式(格式化時間+Decimal轉float)
products_list = []
for item in page_obj.object_list:
products_list.append({
'id': item['id'],
'name': item['name'],
'price': float(item['price']), # Decimal轉float
'created_at': item['created_at'].strftime('%Y-%m-%d %H:%M:%S')
})
# 5. 構造分頁信息
pagination_info = {
'total_count': paginator.count,
'total_pages': paginator.num_pages,
'current_page': page_number,
'has_next': page_obj.has_next(),
'has_previous': page_obj.has_previous(),
'next_page': page_obj.next_page_number() if page_obj.has_next() else None,
'previous_page': page_obj.previous_page_number() if page_obj.has_previous() else None
}
# 6. 返回JSON響應
return JsonResponse({
'status': 'success',
'message': '產品列表獲取成功',
'data': products_list,
'pagination': pagination_info
})
# 非GET請求響應
return JsonResponse({
'status': 'error',
'message': '僅支持GET請求'
}, status=405)
(4)配置 URL 路由(urls.py)
將視圖函數映射到具體的 URL 路徑:
# 項目urls.py(或應用urls.py)
from django.urls import path
from .views import create_product, get_products
urlpatterns = [
# 添加產品接口:POST /api/products/
path('api/products/', create_product, name='create-product'),
# 獲取產品列表接口:GET /api/products/list/
path('api/products/list/', get_products, name='get-products'),
]

浙公網安備 33010602011771號