文章

Django - 前端整合

Django 作為後端框架,與前端框架如 Vue 3 結合可以構建現代化的全棧應用。本節課程將教你如何將 Django 作為 RESTful API 服務,並使用 Vue 3 + Vite + TypeScript 作為前端,實現前後端分離的架構。


課程目標

  1. 瞭解 Django 與前端框架整合的基本原則。
  2. 使用 Vue 3 與 Django 的 REST API 進行數據交互。
  3. 設置跨域資源共享(CORS)以支持前端請求。
  4. 實現基本的用戶認證與數據操作功能。

課程內容

1. Django 配置跨域資源共享(CORS)

安裝 Django-CORS-Headers

後端需要允許來自前端的跨域請求:

1
pip install django-cors-headers

設定 CORS

settings.py 中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
INSTALLED_APPS += [
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    *MIDDLEWARE,
]

# 允許所有來源(僅用於開發環境)
CORS_ALLOW_ALL_ORIGINS = True

# 如果需要限制來源,使用以下方式:
# CORS_ALLOWED_ORIGINS = [
#     "http://localhost:5173",  # Vue 開發伺服器
# ]

2. 前端項目初始化

使用 Vite 創建 Vue 3 專案

在終端中執行:

1
2
3
npm create vite@latest my-project --template vue-ts
cd my-project
npm install

安裝必要的依賴

1
npm install axios vue-router@4 pinia

運行開發伺服器

1
npm run dev

3. 前端項目結構

基本目錄結構如下:

1
2
3
4
5
6
7
8
9
my-project/
├── src/
│   ├── api/               # 存放 API 請求邏輯
│   ├── components/        # 通用元件
│   ├── pages/             # 各個頁面組件
│   ├── router/            # 路由設定
│   ├── stores/            # Pinia 狀態管理
│   ├── App.vue            # 主應用組件
│   └── main.ts            # 應用入口

4. 集成 Django REST API

配置 Axios

src/api/axios.ts 中:

1
2
3
4
5
6
7
8
import axios from "axios";

const apiClient = axios.create({
  baseURL: "http://127.0.0.1:8000/api/", // Django API 的基礎 URL
  timeout: 10000,
});

export default apiClient;

5. 實現用戶認證功能

Django 後端:API 登錄路由

urls.py 中添加:

1
2
3
4
5
6
7
8
9
from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns += [
    path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]

前端:用戶狀態管理

  1. 創建 Pinia Store
    src/stores/auth.ts 中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { defineStore } from "pinia";
import apiClient from "../api/axios";

export const useAuthStore = defineStore("auth", {
  state: () => ({
    accessToken: localStorage.getItem("accessToken") || "",
    refreshToken: localStorage.getItem("refreshToken") || "",
  }),
  actions: {
    async login(username: string, password: string) {
      try {
        const response = await apiClient.post("token/", { username, password });
        this.accessToken = response.data.access;
        this.refreshToken = response.data.refresh;
        localStorage.setItem("accessToken", this.accessToken);
        localStorage.setItem("refreshToken", this.refreshToken);
      } catch (error) {
        console.error("Login failed:", error);
      }
    },
    logout() {
      this.accessToken = "";
      this.refreshToken = "";
      localStorage.removeItem("accessToken");
      localStorage.removeItem("refreshToken");
    },
  },
});

前端:用戶登入頁面

src/pages/Login.vue 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template>
  <div>
    <h1>登入</h1>
    <form @submit.prevent="handleLogin">
      <input v-model="username" placeholder="用戶名" />
      <input v-model="password" type="password" placeholder="密碼" />
      <button type="submit">登入</button>
    </form>
  </div>
</template>

<script lang="ts">
import { ref } from "vue";
import { useAuthStore } from "../stores/auth";

export default {
  setup() {
    const authStore = useAuthStore();
    const username = ref("");
    const password = ref("");

    const handleLogin = async () => {
      await authStore.login(username.value, password.value);
    };

    return { username, password, handleLogin };
  },
};
</script>

6. 整合文章數據

前端:文章列表 API 請求

src/api/posts.ts 中:

1
2
3
4
5
6
import apiClient from "./axios";

export const fetchPosts = async () => {
  const response = await apiClient.get("posts/");
  return response.data;
};

前端:文章列表頁面

src/pages/Posts.vue 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
  <div>
    <h1>文章列表</h1>
    <ul>
      <li v-for="post in posts" :key="post.id">{{ post.title }}</li>
    </ul>
  </div>
</template>

<script lang="ts">
import { ref, onMounted } from "vue";
import { fetchPosts } from "../api/posts";

export default {
  setup() {
    const posts = ref([]);

    onMounted(async () => {
      posts.value = await fetchPosts();
    });

    return { posts };
  },
};
</script>

7. 配置路由

src/router/index.ts 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createRouter, createWebHistory } from "vue-router";
import Login from "../pages/Login.vue";
import Posts from "../pages/Posts.vue";

const routes = [
  { path: "/", component: Posts },
  { path: "/login", component: Login },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

課堂練習

  1. 在前端實現文章的增刪改查功能,並測試 API 整合是否順暢。
  2. 實現用戶登出功能,清除本地存儲的 Token 並返回登入頁。
  3. 優化文章列表頁面,顯示每篇文章的作者與發布時間。

作業

  1. 整合標籤與分類數據,實現在前端顯示文章的分類與標籤。
  2. 實現用戶權限控制,僅允許登入用戶訪問特定頁面。
  3. 使用 Vue 的組件化設計,將通用功能抽取為獨立組件。

本文章以 CC BY 4.0 授權