Files
inventory/templates/stats.html
wangbeihong 6f4a8d82f3 feat:增强框类型管理和搜索功能
- 引入基于 JSON 的框类型覆盖,允许动态更新标签、描述和前缀。
- 增加了一种可调节容量的定制盒型。
- 实现了应用和保存盒子类型覆盖的函数。
- 更新仪表盘,显示按箱型分组的库存低库存商品。
- 创建了一个新的搜索页面,方便快速访问具有增强搜索功能的组件。
- 用搜索页面取代扫描页面,将出站功能直接集成到搜索结果中。
- 改进的界面元素,提升导航和用户体验,包括新增按钮和样式。
- 移除过时的 scanner.js 文件并将其功能集成到搜索页面。
- 更新了各种模板,以反映新的搜索和框类型管理功能。
2026-03-11 16:01:11 +08:00

182 lines
8.4 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>库存统计</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<header class="hero slim">
<div>
<h1>库存统计</h1>
<p>仅展示核心指标: 数量、分类、变动</p>
</div>
<nav class="hero-actions">
<a class="btn btn-light" href="{{ url_for('index') }}">返回首页</a>
<a class="btn btn-light" href="{{ url_for('search_page') }}">快速搜索</a>
</nav>
</header>
<main class="container">
{% if notice %}
<p class="notice">{{ notice }}</p>
{% endif %}
<section class="panel stats-filters">
<form class="stats-filter-form" method="get" action="{{ url_for('stats_page') }}">
<label for="box_type" class="muted">分类筛选</label>
<select id="box_type" name="box_type">
<option value="all" {% if box_type_filter == 'all' %}selected{% endif %}>全部分类</option>
{% for key, meta in box_types.items() %}
<option value="{{ key }}" {% if box_type_filter == key %}selected{% endif %}>{{ meta.label }}</option>
{% endfor %}
</select>
<input type="hidden" name="days" value="{{ days }}">
<button class="btn" type="submit">应用筛选</button>
<a class="btn btn-light" href="{{ url_for('stats_export_csv', days=days, box_type=box_type_filter) }}">导出CSV</a>
</form>
<div class="card-actions">
<form method="post" action="{{ url_for('clear_stats_logs') }}" onsubmit="return confirm('确认清除当前筛选的统计日志吗?不会删除库存数据。');">
<input type="hidden" name="days" value="{{ days }}">
<input type="hidden" name="box_type" value="{{ box_type_filter }}">
<input type="hidden" name="scope" value="current">
<button class="btn btn-light" type="submit">清除当前筛选统计</button>
</form>
<form method="post" action="{{ url_for('clear_stats_logs') }}" onsubmit="return confirm('确认清除全部统计日志吗?不会删除库存数据。');">
<input type="hidden" name="days" value="{{ days }}">
<input type="hidden" name="box_type" value="all">
<input type="hidden" name="scope" value="all">
<button class="btn btn-danger" type="submit">清除全部统计</button>
</form>
</div>
</section>
<section class="metrics-grid">
<article class="metric-card">
<p class="metric-title">{% if box_type_filter == 'all' %}库存总量{% else %}分类库存量{% endif %}</p>
<p class="metric-value">{{ total_quantity }}</p>
{% if box_type_filter != 'all' %}
<p class="hint">占总库存 {{ inventory_share }}%</p>
{% endif %}
</article>
<article class="metric-card">
<p class="metric-title">周期操作次数</p>
<p class="metric-value">{{ period_operation_count }}</p>
</article>
<article class="metric-card">
<p class="metric-title">活跃天数</p>
<p class="metric-value">{{ active_days }}/{{ days }}</p>
</article>
<article class="metric-card">
<p class="metric-title">{{ days }}天净变动</p>
<p class="metric-value">{% if period_net_change > 0 %}+{% endif %}{{ period_net_change }}</p>
</article>
</section>
<section class="panel stats-tabs">
<a class="btn {% if days != 7 %}btn-light{% endif %}" href="{{ url_for('stats_page', days=7, box_type=box_type_filter) }}">近7天</a>
<a class="btn {% if days != 30 %}btn-light{% endif %}" href="{{ url_for('stats_page', days=30, box_type=box_type_filter) }}">近30天</a>
<span class="hint">趋势基于库存变动日志实时计算,包含新增、快速入库、启停、删除的数量变化。</span>
</section>
<section class="stats-layout">
<article class="panel trend-panel">
<h2>库存变动趋势</h2>
<div class="trend-chart" role="img" aria-label="库存变动折线图">
<svg viewBox="0 0 520 180" preserveAspectRatio="none">
<polyline points="{{ trend_polyline }}" />
</svg>
<div class="trend-axis">
{% if trend_points %}
<span>{{ trend_points[0].label }}</span>
<span>{{ trend_points[-1].label }}</span>
{% endif %}
</div>
</div>
<p class="hint">区间最小值 {{ min_value }} | 区间最大值 {{ max_value }}</p>
</article>
<article class="panel">
<h2>{{ chart_title }}</h2>
<div class="chart-list">
{% for item in chart_rows %}
{% set width = 0 %}
{% if max_chart_quantity > 0 %}
{% set width = (item.quantity * 100 / max_chart_quantity)|round(0, 'floor') %}
{% endif %}
<div class="chart-row">
<span>{{ item.label }}</span>
<div class="bar-track" role="img" aria-label="{{ item.label }}库存占比">
<div class="bar-fill" data-width="{{ width }}"></div>
</div>
<span>{{ item.quantity }}</span>
</div>
{% endfor %}
</div>
</article>
</section>
<section class="panel">
<h2>分类趋势快照</h2>
<div class="mini-trend-grid">
{% for row in box_type_series %}
<article class="mini-trend-card">
<p class="metric-title">{{ row.label }}</p>
<svg viewBox="0 0 220 56" preserveAspectRatio="none" class="mini-trend-svg" role="img" aria-label="{{ row.label }}趋势图">
<polyline points="{{ row.sparkline }}"></polyline>
</svg>
<p class="hint">当前 {{ row.latest }} | 净变动 {% if row.delta > 0 %}+{% endif %}{{ row.delta }}</p>
</article>
{% endfor %}
</div>
</section>
<section class="panel">
<h2>最近操作</h2>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>时间</th>
<th>类型</th>
<th>分类</th>
<th>料号</th>
<th>变动</th>
</tr>
</thead>
<tbody>
{% for row in activity_rows %}
<tr>
<td>{{ row.time }}</td>
<td>{{ row.type }}</td>
<td>{{ row.box_type }}</td>
<td>{{ row.part_no }}</td>
<td>{% if row.delta > 0 %}+{% endif %}{{ row.delta }}</td>
</tr>
{% else %}
<tr>
<td colspan="5">暂无操作日志,先进行一次入库或编辑。</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
</main>
<script>
(function () {
document.querySelectorAll('.bar-fill[data-width]').forEach(function (el) {
var value = Number(el.dataset.width || 0);
if (Number.isNaN(value)) {
value = 0;
}
value = Math.max(0, Math.min(100, value));
el.style.width = value + '%';
});
})();
</script>
</body>
</html>