Files
TakeoutSaaS.Prototypes/pages/stat-order.html

300 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 订单分析 stat-order -->
<style>
.so-page { display:flex; flex-direction:column; gap:20px; }
/* 筛选栏 */
.so-filter { display:flex; align-items:center; gap:16px; flex-wrap:wrap; }
.so-date-input { height:34px; padding:0 10px; border:1px solid var(--g-border); border-radius:var(--g-radius-sm); font-size:13px; color:var(--g-text); outline:none; transition:var(--g-transition); }
.so-date-input:focus { border-color:var(--primary); }
.so-date-sep { color:var(--g-text-muted); font-size:13px; }
.so-quick-btns { display:flex; gap:6px; }
.so-quick-btn { height:30px; padding:0 12px; border:1px solid var(--g-border); border-radius:var(--g-radius-sm); background:#fff; font-size:12px; color:var(--g-text-secondary); cursor:pointer; transition:var(--g-transition); }
.so-quick-btn:hover { border-color:var(--primary); color:var(--primary); }
.so-quick-btn.active { background:var(--primary-light); border-color:var(--primary); color:var(--primary); font-weight:500; }
.so-channel-btns { display:flex; gap:0; margin-left:auto; }
.so-channel-btn { height:30px; padding:0 14px; border:1px solid var(--g-border); background:#fff; font-size:12px; color:var(--g-text-secondary); cursor:pointer; transition:var(--g-transition); }
.so-channel-btn:first-child { border-radius:var(--g-radius-sm) 0 0 var(--g-radius-sm); }
.so-channel-btn:last-child { border-radius:0 var(--g-radius-sm) var(--g-radius-sm) 0; }
.so-channel-btn + .so-channel-btn { border-left:none; }
.so-channel-btn:hover { color:var(--primary); }
.so-channel-btn.active { background:var(--primary); border-color:var(--primary); color:#fff; }
/* 统计卡片 */
.so-kpi-row { display:grid; grid-template-columns:repeat(4,1fr); gap:16px; }
.so-kpi { border-radius:var(--g-radius); padding:20px 24px; background:#fff; border:1px solid var(--g-border); transition:var(--g-transition); display:flex; flex-direction:column; gap:8px; }
.so-kpi:hover { box-shadow:var(--g-shadow-md); }
.so-kpi-label { font-size:13px; color:var(--g-text-muted); display:flex; align-items:center; gap:6px; }
.so-kpi-label i { width:16px; height:16px; }
.so-kpi-value { font-size:28px; font-weight:700; color:var(--g-text); letter-spacing:-0.5px; }
.so-kpi-sub { font-size:12px; color:var(--g-text-muted); display:flex; align-items:center; gap:4px; }
.so-kpi-sub .up { color:var(--g-success); font-weight:500; }
.so-kpi-sub .down { color:var(--g-danger); font-weight:500; }
/* Section 标题 */
.so-section-hd { font-size:15px; font-weight:600; color:var(--g-text); padding-left:10px; border-left:3px solid var(--primary); margin-bottom:16px; }
/* 趋势图 */
.so-chart-wrap { position:relative; }
.so-chart-svg { width:100%; height:220px; }
.so-chart-summary { display:flex; gap:32px; margin-top:12px; }
.so-chart-stat { font-size:13px; color:var(--g-text-secondary); }
.so-chart-stat strong { color:var(--g-text); font-weight:600; }
/* 热力图 */
.so-heat-wrap { display:flex; gap:20px; align-items:flex-start; }
.so-heat-grid { display:grid; grid-template-columns:48px repeat(14, 36px); gap:2px; }
.so-heat-header { font-size:11px; color:var(--g-text-muted); display:flex; align-items:center; justify-content:center; height:20px; }
.so-heat-row-label { font-size:12px; color:var(--g-text-secondary); display:flex; align-items:center; height:28px; }
.so-heat-cell { width:36px; height:28px; border-radius:3px; transition:var(--g-transition); cursor:default; }
.so-heat-cell:hover { outline:2px solid var(--primary); outline-offset:-1px; }
.so-heat-l0 { background:hsl(212,60%,95%); }
.so-heat-l1 { background:hsl(212,70%,82%); }
.so-heat-l2 { background:hsl(212,80%,65%); }
.so-heat-l3 { background:hsl(212,90%,45%); }
.so-heat-legend { display:flex; flex-direction:column; gap:6px; padding-top:24px; }
.so-heat-legend-item { display:flex; align-items:center; gap:6px; font-size:12px; color:var(--g-text-secondary); }
.so-heat-legend-dot { width:16px; height:12px; border-radius:2px; }
/* 渠道对比 */
.so-channel-row { display:grid; grid-template-columns:repeat(3,1fr); gap:16px; }
.so-ch-card { border-radius:var(--g-radius); padding:24px; background:#fff; border:1px solid var(--g-border); transition:var(--g-transition); }
.so-ch-card:hover { box-shadow:var(--g-shadow-md); }
.so-ch-hd { display:flex; align-items:center; gap:10px; margin-bottom:16px; }
.so-ch-icon { width:36px; height:36px; border-radius:var(--g-radius-sm); display:flex; align-items:center; justify-content:center; }
.so-ch-icon.waimai { background:hsl(212,100%,95%); color:var(--primary); }
.so-ch-icon.ziti { background:hsl(150,60%,94%); color:var(--g-success); }
.so-ch-icon.tangshi { background:hsl(30,90%,94%); color:var(--g-warning); }
.so-ch-name { font-size:15px; font-weight:600; color:var(--g-text); }
.so-ch-metrics { display:flex; flex-direction:column; gap:10px; margin-bottom:16px; }
.so-ch-metric { display:flex; justify-content:space-between; font-size:13px; }
.so-ch-metric-label { color:var(--g-text-muted); }
.so-ch-metric-val { color:var(--g-text); font-weight:500; }
.so-ch-bar-bg { height:8px; background:#f0f0f0; border-radius:4px; overflow:hidden; }
.so-ch-bar-fill { height:100%; border-radius:4px; transition:width 0.6s cubic-bezier(.2,0,0,1); }
.so-ch-bar-fill.waimai { background:linear-gradient(90deg,#1890ff,#69c0ff); }
.so-ch-bar-fill.ziti { background:linear-gradient(90deg,#52c41a,#95de64); }
.so-ch-bar-fill.tangshi { background:linear-gradient(90deg,#fa8c16,#ffc069); }
</style>
<div class="so-page" style="padding:4px 0;">
<!-- 顶部筛选 -->
<div class="g-card" style="padding:16px 20px;">
<div class="so-filter">
<input type="date" class="so-date-input" value="2026-01-30">
<span class="so-date-sep"></span>
<input type="date" class="so-date-input" value="2026-02-12">
<div class="so-quick-btns">
<button class="so-quick-btn" onclick="pickQuick(this)">今天</button>
<button class="so-quick-btn" onclick="pickQuick(this)">近7天</button>
<button class="so-quick-btn active" onclick="pickQuick(this)">近14天</button>
<button class="so-quick-btn" onclick="pickQuick(this)">本月</button>
</div>
<div class="so-channel-btns">
<button class="so-channel-btn active" onclick="pickChannel(this)">全部</button>
<button class="so-channel-btn" onclick="pickChannel(this)">外卖</button>
<button class="so-channel-btn" onclick="pickChannel(this)">自提</button>
<button class="so-channel-btn" onclick="pickChannel(this)">堂食</button>
</div>
</div>
</div>
<!-- 统计卡片 -->
<div class="so-kpi-row">
<div class="so-kpi">
<div class="so-kpi-label"><i data-lucide="file-text" style="width:16px;height:16px;"></i> 总订单数</div>
<div class="so-kpi-value">3,420</div>
<div class="so-kpi-sub">较上期 <span class="up">+5.2%</span></div>
</div>
<div class="so-kpi">
<div class="so-kpi-label"><i data-lucide="circle-check" style="width:16px;height:16px;"></i> 有效订单</div>
<div class="so-kpi-value">3,286</div>
<div class="so-kpi-sub">完成率 <strong style="color:var(--g-success);">96.1%</strong></div>
</div>
<div class="so-kpi">
<div class="so-kpi-label"><i data-lucide="banknote" style="width:16px;height:16px;"></i> 总销售额</div>
<div class="so-kpi-value">¥128,600</div>
<div class="so-kpi-sub">较上期 <span class="up">+8.7%</span></div>
</div>
<div class="so-kpi">
<div class="so-kpi-label"><i data-lucide="receipt" style="width:16px;height:16px;"></i> 平均客单价</div>
<div class="so-kpi-value">¥39.1</div>
<div class="so-kpi-sub">环比 <span class="up">+2.3%</span></div>
</div>
</div>
<!-- 区块1: 订单趋势 -->
<div class="g-card" style="padding:20px 24px;">
<div class="so-section-hd">订单趋势</div>
<div class="so-chart-wrap">
<svg class="so-chart-svg" viewBox="0 0 700 200" preserveAspectRatio="none">
<!-- 网格线 -->
<line x1="40" y1="10" x2="40" y2="170" stroke="#f0f0f0" stroke-width="1"/>
<line x1="40" y1="170" x2="690" y2="170" stroke="#f0f0f0" stroke-width="1"/>
<line x1="40" y1="130" x2="690" y2="130" stroke="#f5f5f5" stroke-width="1" stroke-dasharray="4"/>
<line x1="40" y1="90" x2="690" y2="90" stroke="#f5f5f5" stroke-width="1" stroke-dasharray="4"/>
<line x1="40" y1="50" x2="690" y2="50" stroke="#f5f5f5" stroke-width="1" stroke-dasharray="4"/>
<!-- Y轴标签 -->
<text x="34" y="174" text-anchor="end" font-size="10" fill="#999">100</text>
<text x="34" y="134" text-anchor="end" font-size="10" fill="#999">200</text>
<text x="34" y="94" text-anchor="end" font-size="10" fill="#999">300</text>
<text x="34" y="54" text-anchor="end" font-size="10" fill="#999">400</text>
<!-- 面积 -->
<polygon points="87,98 133,106 180,112 226,118 273,130 319,148 366,138 412,78 459,92 505,102 552,96 598,108 645,104 690,100 690,170 87,170"
fill="url(#soGrad)" opacity="0.15"/>
<defs>
<linearGradient id="soGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="hsl(212,100%,45%)"/>
<stop offset="100%" stop-color="hsl(212,100%,45%)" stop-opacity="0"/>
</linearGradient>
</defs>
<!-- 折线 -->
<polyline points="87,98 133,106 180,112 226,118 273,130 319,148 366,138 412,78 459,92 505,102 552,96 598,108 645,104 690,100"
fill="none" stroke="var(--primary)" stroke-width="2.5" stroke-linejoin="round" stroke-linecap="round"/>
<!-- 关键数据点 -->
<circle cx="319" cy="148" r="4" fill="#fff" stroke="var(--g-danger)" stroke-width="2"/>
<text x="319" y="164" text-anchor="middle" font-size="10" fill="var(--g-danger)">186</text>
<circle cx="412" cy="78" r="4" fill="#fff" stroke="var(--g-success)" stroke-width="2"/>
<text x="412" y="72" text-anchor="middle" font-size="10" fill="var(--g-success)">312</text>
<circle cx="690" cy="100" r="4" fill="#fff" stroke="var(--primary)" stroke-width="2"/>
<text x="690" y="94" text-anchor="middle" font-size="10" fill="var(--primary)">248</text>
<!-- X轴日期 -->
<text x="87" y="186" text-anchor="middle" font-size="10" fill="#999">1/30</text>
<text x="180" y="186" text-anchor="middle" font-size="10" fill="#999">2/1</text>
<text x="273" y="186" text-anchor="middle" font-size="10" fill="#999">2/3</text>
<text x="319" y="186" text-anchor="middle" font-size="10" fill="#999">2/5</text>
<text x="412" y="186" text-anchor="middle" font-size="10" fill="#999">2/8</text>
<text x="505" y="186" text-anchor="middle" font-size="10" fill="#999">2/10</text>
<text x="645" y="186" text-anchor="middle" font-size="10" fill="#999">2/11</text>
<text x="690" y="186" text-anchor="middle" font-size="10" fill="#999">2/12</text>
</svg>
</div>
<div class="so-chart-summary">
<div class="so-chart-stat">日均订单 <strong>244</strong></div>
<div class="so-chart-stat">峰值 <strong>312</strong>2/8</div>
<div class="so-chart-stat">谷值 <strong>186</strong>2/5</div>
</div>
</div>
<!-- 区块2: 时段分布热力图 -->
<div class="g-card" style="padding:20px 24px;">
<div class="so-section-hd">时段分布</div>
<div class="so-heat-wrap">
<div class="so-heat-grid" id="heatGrid"></div>
<div class="so-heat-legend">
<div class="so-heat-legend-item"><div class="so-heat-legend-dot so-heat-l0"></div></div>
<div class="so-heat-legend-item"><div class="so-heat-legend-dot so-heat-l1"></div>较少</div>
<div class="so-heat-legend-item"><div class="so-heat-legend-dot so-heat-l2"></div>较多</div>
<div class="so-heat-legend-item"><div class="so-heat-legend-dot so-heat-l3"></div></div>
</div>
</div>
</div>
<!-- 区块3: 渠道对比 -->
<div class="g-card" style="padding:20px 24px;">
<div class="so-section-hd">渠道对比</div>
<div class="so-channel-row">
<!-- 外卖 -->
<div class="so-ch-card">
<div class="so-ch-hd">
<div class="so-ch-icon waimai"><i data-lucide="bike" style="width:20px;height:20px;"></i></div>
<div class="so-ch-name">外卖</div>
<span class="g-tag g-tag-blue" style="margin-left:auto;">主力渠道</span>
</div>
<div class="so-ch-metrics">
<div class="so-ch-metric"><span class="so-ch-metric-label">订单数</span><span class="so-ch-metric-val">1,881</span></div>
<div class="so-ch-metric"><span class="so-ch-metric-label">销售额</span><span class="so-ch-metric-val">¥70,730</span></div>
<div class="so-ch-metric"><span class="so-ch-metric-label">客单价</span><span class="so-ch-metric-val">¥37.6</span></div>
<div class="so-ch-metric"><span class="so-ch-metric-label">占比</span><span class="so-ch-metric-val">55%</span></div>
</div>
<div class="so-ch-bar-bg"><div class="so-ch-bar-fill waimai" style="width:55%;"></div></div>
</div>
<!-- 自提 -->
<div class="so-ch-card">
<div class="so-ch-hd">
<div class="so-ch-icon ziti"><i data-lucide="package" style="width:20px;height:20px;"></i></div>
<div class="so-ch-name">自提</div>
</div>
<div class="so-ch-metrics">
<div class="so-ch-metric"><span class="so-ch-metric-label">订单数</span><span class="so-ch-metric-val">855</span></div>
<div class="so-ch-metric"><span class="so-ch-metric-label">销售额</span><span class="so-ch-metric-val">¥32,150</span></div>
<div class="so-ch-metric"><span class="so-ch-metric-label">客单价</span><span class="so-ch-metric-val">¥37.6</span></div>
<div class="so-ch-metric"><span class="so-ch-metric-label">占比</span><span class="so-ch-metric-val">25%</span></div>
</div>
<div class="so-ch-bar-bg"><div class="so-ch-bar-fill ziti" style="width:25%;"></div></div>
</div>
<!-- 堂食 -->
<div class="so-ch-card">
<div class="so-ch-hd">
<div class="so-ch-icon tangshi"><i data-lucide="utensils" style="width:20px;height:20px;"></i></div>
<div class="so-ch-name">堂食</div>
</div>
<div class="so-ch-metrics">
<div class="so-ch-metric"><span class="so-ch-metric-label">订单数</span><span class="so-ch-metric-val">684</span></div>
<div class="so-ch-metric"><span class="so-ch-metric-label">销售额</span><span class="so-ch-metric-val">¥25,720</span></div>
<div class="so-ch-metric"><span class="so-ch-metric-label">客单价</span><span class="so-ch-metric-val">¥37.6</span></div>
<div class="so-ch-metric"><span class="so-ch-metric-label">占比</span><span class="so-ch-metric-val">20%</span></div>
</div>
<div class="so-ch-bar-bg"><div class="so-ch-bar-fill tangshi" style="width:20%;"></div></div>
</div>
</div>
</div>
</div>
<script>
/* 快捷日期按钮 */
function pickQuick(el) {
el.parentElement.querySelectorAll('.so-quick-btn').forEach(function(b){ b.classList.remove('active'); });
el.classList.add('active');
}
/* 渠道按钮 */
function pickChannel(el) {
el.parentElement.querySelectorAll('.so-channel-btn').forEach(function(b){ b.classList.remove('active'); });
el.classList.add('active');
}
/* 生成热力图 */
(function(){
var days = ['周一','周二','周三','周四','周五','周六','周日'];
var hours = [];
for(var h=9; h<=22; h++) hours.push(h+':00');
var grid = document.getElementById('heatGrid');
// 模拟数据:周末午晚高峰密度高
var data = [
[0,0,1,1,2,1,1,0,0,1,2,2,1,0],
[0,0,1,1,2,1,0,0,0,1,2,1,1,0],
[0,1,1,2,2,1,1,0,1,2,2,2,1,0],
[0,0,1,2,2,1,1,0,1,2,3,2,1,0],
[0,1,2,2,3,2,1,1,2,3,3,2,1,0],
[1,2,2,3,3,2,2,1,2,3,3,3,2,1],
[1,2,2,3,3,2,1,1,2,3,3,2,2,1]
];
// 表头行
var blank = document.createElement('div');
blank.className = 'so-heat-header';
grid.appendChild(blank);
hours.forEach(function(h){
var hd = document.createElement('div');
hd.className = 'so-heat-header';
hd.textContent = h;
grid.appendChild(hd);
});
// 数据行
days.forEach(function(day, di){
var label = document.createElement('div');
label.className = 'so-heat-row-label';
label.textContent = day;
grid.appendChild(label);
data[di].forEach(function(v){
var cell = document.createElement('div');
cell.className = 'so-heat-cell so-heat-l' + v;
grid.appendChild(cell);
});
});
})();
/* Lucide 图标初始化 */
if (typeof lucide !== 'undefined') { lucide.createIcons(); }
</script>