今天我們將實作營業分析功能,讓餐廳管理系統更具洞察力。這個功能將讓您能夠查詢特定日期的營業數據,並以圖表形式呈現。我們將實現以下功能:
首先,我們來看一下營業分析功能的前端界面。以下是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>
// 儲存已繪製的圖表實例
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層,我們建立了一個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
@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層包含了對資料庫的查詢,我們使用自定義的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,實現自動化部署和測試。敬請期待!