iT邦幫忙

2023 iThome 鐵人賽

DAY 29
0
Mobile Development

Spring Boot+Android 30天 實戰開發 系列 第 29

【Day - 29】建立餐廳後台點餐應用05:結合Google Charts實作營業分析功能

  • 分享至 

  • xImage
  •  

今天我們將實作營業分析功能,讓餐廳管理系統更具洞察力。這個功能將讓您能夠查詢特定日期的營業數據,並以圖表形式呈現。我們將實現以下功能:

功能概述

  • 營業分析
    1. 可查詢某一天的營業分析數據。
    2. 顯示該天的總營業額。
    3. 圖表化呈現每個時段的訂單數量、銷售額和累積銷售額。
    4. 圖表化顯示最熱銷的前5名餐點。

View層

首先,我們來看一下營業分析功能的前端界面。以下是HTML模板,用於顯示營業分析的各種資訊:
圖片無法顯示

<!DOCTYPE html>
<html lang="zh-tw">
<head>
    <meta charset="UTF-8">
    <title>營業結果分析</title>

    <!-- 引入所需的CSS和JavaScript文件 -->
    <link rel="stylesheet" href="/styles.css">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <!-- 引入 Google Charts JavaScript 庫 -->
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript" src="/analytics.js"></script>
</head>
<body class="container">

<div th:insert="~{navbar}"></div>

<!-- 營業分析的日期選擇和查詢按鈕 -->
<div class="form-group row mt-4">
    <h5 class="col-sm-1 align-self-center">日期:</h5>
    <div class="col-sm-3">
        <input type="date" class="form-control" id="datePicker" th:value="${#dates.format(#dates.createNow(), 'yyyy-MM-dd')}">
    </div>
    <div class="col-sm-2">
        <button class="btn btn-primary" id="queryBtn">查詢</button>
    </div>
</div>

<!-- 顯示總銷售額的區域 -->
<div id="totalSales" class="mt-4 mb-4"></div>

<!-- 顯示每小時銷售數量的折線圖 -->
<div id="hourlySalesChart"></div>

<!-- 顯示最熱銷餐點的水平條形圖 -->
<div id="topSellingChart"></div>
</body>
</html>
  • Google Chart JS程式碼
// 儲存已繪製的圖表實例
let hourlySalesChart = null;
let topSellingChart = null;

function drawCharts(response) {
    // ------- 每小時銷售數量數據 ---------
    let tmpTotalSales = 0;
    let hourlySalesData = [
        ['Hour', '銷售數量', '銷售額', '累積銷售額'],
    ];
    response.hourlySales.forEach(item => {
        tmpTotalSales += item.total;
        hourlySalesData.push([item.hour+":00", item.count, item.total, tmpTotalSales]);
    });

    // ------- 最熱銷的前5名餐點數據 -------
    let topSellingData = [
        ['Dish', '數量'],
    ];
    response.dishRanking.forEach(dish => {
        topSellingData.push([dish.name.trim(), dish.count]);
    });

    // 繪製每小時銷售數量折線圖
    hourlySalesChart = new google.visualization.ComboChart(document.getElementById('hourlySalesChart'));
    hourlySalesChart.draw(google.visualization.arrayToDataTable(hourlySalesData), {
        title: '每小時銷售數量',
        // 銷售數量使用摺疊圖
        series: {
            0: { type: 'bars', targetAxisIndex: 1, color: 'orange'}, // 銷售數量使用第一個 Y 軸
            1: { type: 'line', targetAxisIndex: 0, color: '#934aa8'},  // 銷售額使用第二個 Y 軸
            2: { type: 'area', targetAxisIndex: 0, color: '#9fa39b'},
        },
        vAxes: {
            0: { title: '銷售額', format: 'currency' },
            1: { title: '銷售數量'},
        },
        hAxis: { title: 'Hour' }
    });

    // 繪製最熱銷餐點水平條形圖
    topSellingChart = new google.visualization.BarChart(document.getElementById('topSellingChart'));
    topSellingChart.draw(google.visualization.arrayToDataTable(topSellingData), {
        title: '最熱銷的前5名餐點',
        legend: { position: "bottom" },
    });
}

$(document).ready(function() {
    // 載入 Google Charts API
    google.charts.load('current', {
        packages: ['corechart']
    });
    getAnalyticsData($('#datePicker').val());

    $('#queryBtn').click(function() {
        let selectedDate = $('#datePicker').val();
        if (selectedDate === null || selectedDate === "") {
            alert("請輸入要查詢的日期");
            return;
        }
        getAnalyticsData(selectedDate);
    })
});

function updateTotalSales(totalSales) {
    // 顯示總銷售額
    $('#totalSales').html('<h5>當天總銷售額: $' + totalSales.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + '</h5>');
}

// 獲取營業分析數據
function getAnalyticsData(selectedDate) {
    $.ajax({
        url: '/api/analytics',
        method: 'GET',
        data: { date: selectedDate },
        success: function(response) {
            console.log(response);
            // 當天的總銷售額數據
            let totalSales = response.totalSales;
            if(totalSales === null || totalSales === 0) {
                updateTotalSales(0);
                clearCharts(); // 清除圖表
            } else {
                updateTotalSales(totalSales);
                // 在這裡處理後端返回的數據,例如更新圖表或顯示相關資訊
                google.charts.setOnLoadCallback(function() {
                    drawCharts(response);
                });
            }
        },
        error: function(error) {
            console.error('Error:', error);
        }
    });
}

// 清除圖表
function clearCharts() {
    if (hourlySalesChart !== null) {
        hourlySalesChart.clearChart();
    }
    if (topSellingChart !== null) {
        topSellingChart.clearChart();
    }
}

Controller層

在Controller層,我們建立了一個RESTful API端點,用於處理營業分析數據的請求:

@RestController
@RequestMapping("/api/analytics")
@RequiredArgsConstructor
class AnalyticsRestController {
    private final AnalyticsService analyticsService;

    @GetMapping
    public ResponseEntity<?> getData(@RequestParam("date") LocalDate date) {
        return ResponseEntity.ok(analyticsService.getData(date));
    }
}

Service層

Service層包含了營業分析功能的主要邏輯。我們從資料庫中擷取所需的數據,並轉換為前端所需的格式:

@Service
@RequiredArgsConstructor
public class AnalyticsService {

    private final OrderRepository orderRepository;

    public AnalyticsResponse getData(LocalDate date) {

        // 取得銷量前5名的餐點
        List<Object> objectList = orderRepository.getTop5DishRanking(date); // 使用自己的方法獲取List<Object>結果
        List<AnalyticsResponse.DishRanking> dishRankingList = new ArrayList<>();
        for (Object obj : objectList) {
            Object[] result = (Object[]) obj;
            String name = (String) result[0];
            Integer count = (Integer) result[1];
            AnalyticsResponse.DishRanking dishRanking = new AnalyticsResponse.DishRanking(name, count);
            dishRankingList.add(dishRanking);
        }

        // 取得每小時的銷量次數和總銷售額
        List<Object> objectHourlySalesList = orderRepository.getHourlySales(date);
        List<AnalyticsResponse.HourlySales> hourlySalesList = new ArrayList<>();
        for (Object obj : objectHourlySalesList) {
            Object[] result = (Object[]) obj;
            Integer hour = (Integer) result[0];
            Integer count = (Integer) result[1];
            Integer total = (Integer) result[2];
            AnalyticsResponse.HourlySales hourlySales = new AnalyticsResponse.HourlySales(hour, count, total);
            hourlySalesList.add(hourlySales);
        }

        return AnalyticsResponse.builder()
                .totalSales(orderRepository.getTotalSalesAmount(date))
                .dishRanking(dishRankingList)
                .hourlySales(hourlySalesList)
                .build();
    }
}

Repository層

Repository層包含了對資料庫的查詢,我們使用自定義的SQL查詢獲取所需的數據:

@Repository
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {

    @Override
    @Query("SELECT o FROM _order o ORDER BY o.dateTime DESC")
    List<OrderEntity> findAll();

    @Query(value = "SELECT SUM(o.totalPrice)FROM _order o WHERE CAST(o.dateTime AS DATE) = :date")
    Integer getTotalSalesAmount(@Param("date") LocalDate date);

    @Query(value = """
        SELECT TOP(5) d.name, t.count 
        FROM (SELECT od.dish_id as dish_id, sum(count) as count 
            FROM _order o 
            LEFT JOIN order_detail od ON o.id = od.order_id 
            WHERE CAST(o.date_time AS DATE) = :date
            GROUP BY od.dish_id 
        ) t LEFT JOIN dish d ON t.dish_id = d.id 
        ORDER BY t.count DESC
    """, nativeQuery = true)
    List<Object> getTop5DishRanking(@Param("date") LocalDate date);

    @Query(value = """
        SELECT DATEPART(HOUR, o.date_time) as hour, count(*) as saleCount, sum(o.total_price) as totalSales
        FROM _order o
        WHERE CAST(o.date_time AS DATE) = :date
        GROUP BY DATEPART(HOUR, o.date_time)
    """, nativeQuery = true)
    List<Object> getHourlySales(@Param("date") LocalDate date);
}

小結

在這篇文章中,我們成功實作了營業分析功能,讓餐廳管理系統更具效能和可視化能力。這個功能將有助於餐廳管理團隊更好地了解營業數據並做出相應的決策。在明天的文章中,我們將介紹如何將這個項目結合GitLab CI/CD,實現自動化部署和測試。敬請期待!
/images/emoticon/emoticon08.gif


上一篇
【Day - 28】建立餐廳後台點餐應用04:實作新增&管理訂單功能
下一篇
【Day - 30】建立餐廳後台點餐應用06:結合GitLab CI/CD
系列文
Spring Boot+Android 30天 實戰開發 30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言