Initial commit - Zhangmenu Django project

A menu management application built with Django, featuring:
- Dish management with cover images
- Order system with party dates and participants
- REST API with Django REST Framework
- Bootstrap-styled frontend

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Developer
2026-03-12 11:27:23 +08:00
co-authored by Claude Opus 4.6
commit 0a50c09dba
47 changed files with 10268 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
// 统计页面模块
const StatsPage = {
// 初始化
init() {
this.loadStats();
},
// 加载统计数据
async loadStats() {
try {
const [dishesRes, ordersRes] = await Promise.all([
fetch(`${API_BASE}/dishes/`),
fetch(`${API_BASE}/orders/`)
]);
const allDishes = await dishesRes.json();
const allOrders = await ordersRes.json();
// 计算菜品出现次数
const dishCompletedCounts = {};
allOrders
.filter(o => o.status === 'completed')
.forEach(order => {
(order.dishes_detail || []).forEach(dish => {
dishCompletedCounts[dish.id] = (dishCompletedCounts[dish.id] || 0) + 1;
});
});
AppState.setDishCompletedCounts(dishCompletedCounts);
// 统计数据
const totalDishes = allDishes.length;
const completedOrders = allOrders.filter(o => o.status === 'completed').length;
const inProgressOrders = allOrders.filter(o => o.status === 'in_progress').length;
// 统计参与人员
const participantCounts = {};
allOrders.forEach(order => {
(order.participants || []).forEach(p => {
participantCounts[p] = (participantCounts[p] || 0) + 1;
});
});
const uniqueParticipants = Object.keys(participantCounts).length;
// 渲染统计卡片
this.renderStatCards({ totalDishes, completedOrders, inProgressOrders, uniqueParticipants });
// 渲染菜品排行榜
this.renderDishRank(allDishes, dishCompletedCounts);
} catch (e) {
console.error('加载统计失败:', e);
}
},
// 渲染统计卡片
renderStatCards(stats) {
const container = document.getElementById('stats-cards');
if (!container) return;
container.innerHTML = `
<div class="stat-card">
<div class="stat-icon" style="background: linear-gradient(135deg, var(--primary-soft), #fef3c7); color: var(--primary);">
<i class="bi bi-book"></i>
</div>
<div class="stat-value">${stats.totalDishes}</div>
<div class="stat-label">菜品总数</div>
</div>
<div class="stat-card">
<div class="stat-icon" style="background: var(--secondary-soft); color: var(--secondary);">
<i class="bi bi-check-circle"></i>
</div>
<div class="stat-value">${stats.completedOrders}</div>
<div class="stat-label">已完成聚会</div>
</div>
<div class="stat-card">
<div class="stat-icon" style="background: var(--accent-soft); color: #b45309;">
<i class="bi bi-clock"></i>
</div>
<div class="stat-value">${stats.inProgressOrders}</div>
<div class="stat-label">进行中</div>
</div>
`;
},
// 渲染菜品排行榜
renderDishRank(dishes, counts) {
const container = document.getElementById('dish-rank-list');
if (!container) return;
// 按出现次数排序
const dishesWithCounts = dishes.map(dish => ({
...dish,
orderCount: counts[dish.id] || 0
})).filter(d => d.orderCount > 0)
.sort((a, b) => b.orderCount - a.orderCount);
if (dishesWithCounts.length === 0) {
container.innerHTML = `
<div class="empty-rank">
<i class="bi bi-trophy"></i>
<p>暂无数据</p>
</div>
`;
return;
}
container.innerHTML = dishesWithCounts.slice(0, 10).map((dish, index) => {
let rankClass = 'default';
if (index === 0) rankClass = 'gold';
else if (index === 1) rankClass = 'silver';
else if (index === 2) rankClass = 'bronze';
return `
<div class="rank-item">
<span class="rank-number ${rankClass}">${index + 1}</span>
<img class="rank-img" src="${dish.cover_image || getDefaultImage()}" alt="${dish.name}">
<div class="rank-info">
<div class="rank-name">${dish.name}</div>
<div class="rank-count">出现 ${dish.orderCount} 次</div>
</div>
</div>
`;
}).join('');
}
};
// 导出
window.StatsPage = StatsPage;