本文為 留言評分功能 系列教學,第 13 篇:
- 使用 Django + Tailwind + Alpine.js 實作「五顆星評分」功能教學
- Django validators 驗證器完整教學
- Django PositiveSmallIntegerField 新手指南
- 在 esbuild 專案中整合 Alpine.js 的完整指南
- 使用 Alpine.js 建立星星評分表單 — 新手指南
- 深入理解 Alpine.js 中的 template 標籤使用指南
- Django 網站如何新增「星星評分」功能 — 後端接收邏輯
- Django 表單:如何讓使用者選擇性提交星星評分與留言
- Django 如何限制同一使用者只能對同一服務留言一次?
- Django 留言軟刪除邏輯|程式碼解析
- Django 計算評分摘要教學 — 使用 ORM 進行星等統計
- Python Django 使用 annotate、aggregate 統計教學
- 使用 Alpine.js 實作星級評分分佈 – 詳細教學 👈 所在位置
- Alpine.js 與 Tailwind CSS 動態樣式解析:為什麼有些樣式無法生效?
- Django 中使用 annotate() 與 Avg() 進行平均評分計算
這篇文章將詳細解釋如何使用 Alpine.js 搭配 Django 模板,實作一個互動式的星級評分分佈元件。
本範例展示了如何顯示評論的平均分數、總評論數,以及每個星等的分佈情況,同時使用動態計算進度條的寬度來視覺化呈現數據。
程式碼總覽
以下是完整的 Alpine.js 與 Django 模板代碼,用於渲染星級評分分佈:
<!-- ✅ 使用 Alpine.js 正確渲染星級分佈 -->
<div class="bg-white rounded-lg shadow-lg p-6 mb-6"
x-data="{
averageRating: {{ average_rating|default:'null' }},
totalReviews: {{ total_reviews|default:0 }},
starsCount: {{ stars_count|default:'{}' }},
calculatePercentage(count, total) {
if (!total || !count) return 0;
const percentage = (count / total) * 100;
return percentage.toFixed(1);
},
formatRating(rating) {
return rating ? parseFloat(rating).toFixed(1) : '0.0';
}
}">
<!-- ⭐ 標題 -->
<h2 class="text-2xl font-bold mb-4">評分摘要</h2>
<!-- ⭐ 平均分數 -->
<div class="flex items-center mb-4">
<span
class="font-bold"
x-text="averageRating.toFixed(1)"
style="font-size: 64px;"
>0.0</span>
<div class="ml-4">
<!-- 渲染星星 -->
<div class="flex text-yellow-400 text-3xl">
<template x-for="n in 5">
<span x-text="n <= Math.round(averageRating) ? '★' : '☆'"></span>
</template>
</div>
<p class="text-gray-600" x-text="`${totalReviews} 則評論`"></p>
</div>
</div>
<!-- ⭐ 評分分佈 (調整進度條寬度和對齊) -->
<template x-for="i in 5" :key="i">
<div class="flex items-center mb-2">
<!-- 星級文字 -->
<span class="text-lg font-semibold w-6 text-right" x-text="6-i"></span>
<span class="text-yellow-400 text-2xl ml-2">★</span>
<!-- 進度條 -->
<div class="flex-grow mx-4">
<div class="w-full bg-gray-200 h-4 rounded-lg overflow-hidden">
<div
class="transition-all duration-300"
:style="`
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;
height: 16px;
background-color: #FBBF24;
`"
></div>
</div>
</div>
</div>
</template>
</div>
外層容器:x-data
<div
class="bg-white rounded-lg shadow-lg p-6 mb-6"
x-data="{
averageRating: {{ average_rating|default:'null' }},
totalReviews: {{ total_reviews|default:0 }},
starsCount: {{ stars_count|default:'{}' }},
calculatePercentage(count, total) {
if (!total || !count) return 0;
return ((count / total) * 100).toFixed(1);
},
formatRating(rating) {
return rating ? parseFloat(rating).toFixed(1) : '0.0';
}
}"
>
解釋:
x-data:Alpine.js 的核心,用於初始化元件狀態。averageRating:使用 Django 模板語法將average_rating傳入 Alpine.js 內部作為數據綁定變數。totalReviews:同樣透過 Django 模板變數將總評論數綁定進來。starsCount:儲存每個星等的數量,格式為一個字典 (如{5: 10, 4: 5, 3: 3})。
定義的兩個方法:
calculatePercentage(count, total):計算星等比例 (用於進度條)。formatRating(rating):將評分數值格式化為小數點後一位數。
calculatePercentage() 方法
calculatePercentage(count, total) {
if (!total || !count) return 0;
const percentage = (count / total) * 100;
return percentage.toFixed(1);
}- 計算星等的比例,用於進度條的寬度控制。
- 將 某星等的評論數 (
count) 與 總評論數 (total) 做比例計算,轉換成百分比格式。 - 格式化為 小數點後一位數,方便前端渲染更精確的比例。
📌 步驟拆解
- 輸入檢查
if (!total || !count) return 0;- 如果
total或count為0或未定義,直接回傳0%。 - 這是為了避免「除以零」的錯誤,確保數據安全。
- 如果
- 計算百分比
const percentage = (count / total) * 100;- 將該星等的數量
count除以總評論數total,再乘以100,計算出百分比。
- 將該星等的數量
- 格式化數值
return percentage.toFixed(1);- 使用 JavaScript 內建的
.toFixed(1)將數字保留 小數點後一位。 - 範例:
4.66667→4.7
- 使用 JavaScript 內建的
formatRating() 方法
formatRating(rating) {
return rating ? parseFloat(rating).toFixed(1) : '0.0';
}- 將 平均評分 轉換為標準的 小數點後一位數 格式,適合視覺化顯示評分結果。
- 如果
rating為空或null,則回傳預設值"0.0"。
📌 步驟拆解
- 檢查數值是否存在
return rating ? ...- 如果
rating有值,繼續執行轉換。 - 如果
rating為null或undefined,則回傳"0.0"。
- 如果
- 轉換為浮點數 (小數格式)
parseFloat(rating)- 將
rating轉換為浮點數,確保數值正確計算。 - 範例:
"4.678"→4.678
- 將
- 限制小數位數
.toFixed(1)- 使用
.toFixed(1)將結果限制為小數點後 1 位數。 - 範例:
4.678→4.7
- 使用
- 預設值處理
: '0.0'- 如果
rating為空值 (null或undefined),直接回傳"0.0"。
- 如果
顯示平均分數
<div class="flex items-center mb-4">
<span
class="font-bold"
x-text="averageRating.toFixed(1)"
style="font-size: 64px;">
0.0
</span>
解釋:
x-text (Alpine.js 綁定屬性)
x-text="averageRating.toFixed(1)"x-text是 Alpine.js 的屬性,用於將變數的值直接插入到 HTML 元素中並顯示。- 這裡的
averageRating.toFixed(1)會將averageRating格式化為小數點後 1 位數,並顯示在這個<span>元素內。
0.0 (預設值)
0.0是 靜態 HTML 內容,它僅作為預設值,用於:- 頁面載入時 JavaScript 尚未執行前。
- 如果
x-data尚未初始化完成,這個靜態數值會先顯示。
頁面載入流程 (渲染階段)
載入時發生的流程:
- 頁面初始狀態 (未執行 JavaScript)
- 瀏覽器會直接渲染靜態 HTML。
- 這時候畫面上會顯示
0.0,因為 JavaScript 還未執行。
- Alpine.js 初始化 (
x-data)- Alpine.js 解析
x-data並將數據 (averageRating) 綁定到畫面上。
- Alpine.js 解析
- 數據更新
- 如果
averageRating有值,x-text會立即將0.0替換成計算後的分數,例如4.5。
- 如果
渲染範例 (逐步變化)
<!-- 初始狀態 (JavaScript 尚未執行) -->
<span class="font-bold" style="font-size: 64px;">0.0</span>
<!-- JavaScript 加載完成後 -->
<span class="font-bold" style="font-size: 64px;">4.5</span>
渲染星星
<div class="flex text-yellow-400 text-3xl">
<template x-for="n in 5">
<span x-text="n <= Math.round(averageRating) ? '★' : '☆'"></span>
</template>
</div>解釋:
它的目的是用 Alpine.js 來根據平均評分 (averageRating) 動態渲染「實心星 (★)」或「空心星 (☆)」。
<template x-for> (建立一個循環)
<template x-for="n in 5">➡️ 這行的作用:
x-for="n in 5"表示建立一個從1到5的循環 (5 顆星)。- 會自動生成 5 個
<span>元素,每個元素對應一個星星。 n代表目前的星星索引,從1開始數到5。
x-text (根據條件渲染星星)
<span x-text="n <= Math.round(averageRating) ? '★' : '☆'"></span>➡️ 這行的作用:
x-text是 Alpine.js 的屬性,用於將動態數據插入到元素中。- 這裡的判斷條件:
n <= Math.round(averageRating) ? '★' : '☆'意思是:- 如果
n(第幾顆星) 小於或等於 四捨五入後的平均分數 (Math.round(averageRating)),就顯示實心星 (★)。 - 否則,顯示空心星 (
☆)。
- 如果
範例說明 (平均分數 4.2)
averageRating = 4.2渲染流程:
n = 1→1 <= 4✅ → 顯示★n = 2→2 <= 4✅ → 顯示★n = 3→3 <= 4✅ → 顯示★n = 4→4 <= 4✅ → 顯示★n = 5→5 <= 4❌ → 顯示☆
最終渲染結果:
★★★★☆範例說明 (平均分數 2.7)
averageRating = 2.7渲染流程:
n = 1→1 <= 3✅ → 顯示★n = 2→2 <= 3✅ → 顯示★n = 3→3 <= 3✅ → 顯示★n = 4→4 <= 3❌ → 顯示☆n = 5→5 <= 3❌ → 顯示☆
最終渲染結果:
★★★☆☆顯示總評論數
<p class="text-gray-600" x-text="`${totalReviews} 則評論`"></p>解釋:
- 使用
x-text將totalReviews綁定到畫面中,顯示總評論數。 - 例如:
150 則評論。
渲染星等分佈
<template x-for="i in 5" :key="i">
<div class="flex items-center mb-2">
<span class="text-lg font-semibold w-6 text-right" x-text="6-i"></span>
<span class="text-yellow-400 text-2xl ml-2">★</span>
<!-- 進度條 -->
<div class="flex-grow mx-4">
<div class="w-full bg-gray-200 h-4 rounded-lg overflow-hidden">
<div
class="transition-all duration-300"
:style="`
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;
height: 16px;
background-color: #FBBF24;
`"
></div>
</div>
</div>
</div>
</template>
解釋:
這段程式碼的功能是根據星等的分佈,使用 Alpine.js 動態生成一個進度條,顯示每個星等的佔比。
第一層容器:進度條的父元素
<div class="flex-grow mx-4">解釋:
flex-grow:讓這個元素可以在 Flexbox 中自動填滿剩餘空間。mx-4:這是 Tailwind CSS 的類別,表示「左右外距 (margin) 各 1rem (16px)」。
第二層容器:進度條背景
<div class="w-full bg-gray-200 h-4 rounded-lg overflow-hidden">解釋:
w-full:寬度設為100%(父元素的全部寬度)。bg-gray-200:灰色背景,作為進度條的底色。h-4:高度設定為4(對應於1rem = 16px)。rounded-lg:讓進度條有圓角 (大約8px的圓角效果)。overflow-hidden:將超出範圍的內容隱藏,確保進度條不會溢出邊界。
第三層容器 (關鍵):動態控制的進度條
<div
class="transition-all duration-300"
:style="`
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;
height: 16px;
background-color: #FBBF24;
`"
></div>這裡是進度條的核心邏輯:
class="transition-all duration-300"transition-all:表示所有 CSS 屬性都將有動畫效果。duration-300:動畫時間設定為300ms。
動態綁定樣式 (:style)
這裡使用了 Alpine.js 的動態綁定屬性 :style,用來根據數據變化調整進度條的寬度與顏色。
:style="`
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;
height: 16px;
background-color: #FBBF24;
`"
這段程式碼拆解:
- 寬度 (width)
- 使用
calculatePercentage()方法計算進度條寬度。 starsCount[6-i]:取第6-i顆星的數量 (因為是反向渲染從5星到1星)。|| 0:如果starsCount[6-i]沒有值 (undefined),預設為0。
- 使用
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;- 高度 (height)
- 將高度固定為
16px,這與h-4是一樣的效果,但這裡透過內聯樣式設定。
- 將高度固定為
height: 16px;- 顏色 (background-color)
- 進度條的填充顏色設定為黃色 (
#FBBF24),對應於 Tailwind CSS 中的text-yellow-400。
- 進度條的填充顏色設定為黃色 (
background-color: #FBBF24;範例說明 (假設 5 星有 50 人,總評論 100 人)
calculatePercentage(50, 100) // ➡️ 50%width: 50%→ 進度條會填滿一半寬度。
範例 (視覺效果)
假設有以下資料:
starsCount = {5: 50, 4: 30, 3: 10, 2: 5, 1: 5}
totalReviews = 100最終渲染結果 (進度條視覺化):
- 5 星:50% →
★★★★☆+ [███████▒▒▒▒▒] - 4 星:30% →
★★★☆☆+ [████▒▒▒▒▒▒▒▒] - 3 星:10% →
★★☆☆☆+ [██▒▒▒▒▒▒▒▒▒▒] - 2 星:5% →
★☆☆☆☆+ [█▒▒▒▒▒▒▒▒▒▒▒] - 1 星:5% →
★☆☆☆☆+ [█▒▒▒▒▒▒▒▒▒▒▒]
總結
這段 Alpine.js 代碼完成了以下功能:
- 平均分數與星星渲染
- 顯示總評論數
- 計算並渲染星等分佈進度條
- 透過 Django 模板將後端數據傳遞到前端
這樣的設計簡潔且互動性高,適合用於服務評價、電商商品評論等情境。