使用 Alpine.js 實作星級評分分佈 – 詳細教學

更新日期: 2025 年 1 月 7 日

這篇文章將詳細解釋如何使用 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) 做比例計算,轉換成百分比格式。
  • 格式化為 小數點後一位數,方便前端渲染更精確的比例。

📌 步驟拆解

  1. 輸入檢查if (!total || !count) return 0;
    • 如果 totalcount0 或未定義,直接回傳 0%。
    • 這是為了避免「除以零」的錯誤,確保數據安全。
  2. 計算百分比const percentage = (count / total) * 100;
    • 將該星等的數量 count 除以總評論數 total,再乘以 100,計算出百分比。
  3. 格式化數值return percentage.toFixed(1);
    • 使用 JavaScript 內建的 .toFixed(1) 將數字保留 小數點後一位
    • 範例: 4.666674.7

formatRating() 方法

formatRating(rating) {
    return rating ? parseFloat(rating).toFixed(1) : '0.0';
}
  • 平均評分 轉換為標準的 小數點後一位數 格式,適合視覺化顯示評分結果。
  • 如果 rating 為空或 null,則回傳預設值 "0.0"

📌 步驟拆解

  1. 檢查數值是否存在return rating ? ...
    • 如果 rating 有值,繼續執行轉換。
    • 如果 ratingnullundefined,則回傳 "0.0"
  2. 轉換為浮點數 (小數格式)parseFloat(rating)
    • rating 轉換為浮點數,確保數值正確計算。
    • 範例: "4.678"4.678
  3. 限制小數位數.toFixed(1)
    • 使用 .toFixed(1) 將結果限制為小數點後 1 位數
    • 範例: 4.6784.7
  4. 預設值處理: '0.0'
    • 如果 rating 為空值 (nullundefined),直接回傳 "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 尚未初始化完成,這個靜態數值會先顯示。

頁面載入流程 (渲染階段)

載入時發生的流程:

  1. 頁面初始狀態 (未執行 JavaScript)
    • 瀏覽器會直接渲染靜態 HTML。
    • 這時候畫面上會顯示 0.0,因為 JavaScript 還未執行。
  2. Alpine.js 初始化 (x-data)
    • Alpine.js 解析 x-data 並將數據 (averageRating) 綁定到畫面上。
  3. 數據更新
    • 如果 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" 表示建立一個從 15 的循環 (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

渲染流程:

  1. n = 11 <= 4 ✅ → 顯示
  2. n = 22 <= 4 ✅ → 顯示
  3. n = 33 <= 4 ✅ → 顯示
  4. n = 44 <= 4 ✅ → 顯示
  5. n = 55 <= 4 ❌ → 顯示

最終渲染結果:

★★★★☆

範例說明 (平均分數 2.7)

averageRating = 2.7

渲染流程:

  1. n = 11 <= 3 ✅ → 顯示
  2. n = 22 <= 3 ✅ → 顯示
  3. n = 33 <= 3 ✅ → 顯示
  4. n = 44 <= 3 ❌ → 顯示
  5. n = 55 <= 3 ❌ → 顯示

最終渲染結果:

★★★☆☆

顯示總評論數

<p class="text-gray-600" x-text="`${totalReviews} 則評論`"></p>

解釋:

  • 使用 x-texttotalReviews 綁定到畫面中,顯示總評論數。
  • 例如: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;
`"

這段程式碼拆解:

  1. 寬度 (width)
    • 使用 calculatePercentage() 方法計算進度條寬度。
    • starsCount[6-i]:取第 6-i 顆星的數量 (因為是反向渲染從 5 星到 1 星)。
    • || 0:如果 starsCount[6-i] 沒有值 (undefined),預設為 0
width: ${calculatePercentage(starsCount[6-i] || 0, totalReviews)}%;
  1. 高度 (height)
    • 將高度固定為 16px,這與 h-4 是一樣的效果,但這裡透過內聯樣式設定。
height: 16px;
  1. 顏色 (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 代碼完成了以下功能:

  1. 平均分數與星星渲染
  2. 顯示總評論數
  3. 計算並渲染星等分佈進度條
  4. 透過 Django 模板將後端數據傳遞到前端

這樣的設計簡潔且互動性高,適合用於服務評價、電商商品評論等情境。

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *