feat:增强模板的用户界面和功能
- 在 scanner.js 中为用户操作添加了 toast 通知。 - 更新 box.html 以包含额外的导航选项和改进的布局。 - 增强 edit.html,提供更清晰的说明和改进表单的可访问性。 - 修改了 error.html,以提供有关输入错误的用户指导。 - 改进了 index.html,以优化导航并添加了关键指标显示。 - 增强了 scan.html,优化了搜索输入和操作按钮。 - 引入了 stats.html,用于详细的库存统计和趋势。 - 创建了 types.html,用于分类概述库存类型。
This commit is contained in:
@@ -8,34 +8,106 @@
|
||||
</head>
|
||||
<body>
|
||||
<header class="hero">
|
||||
<h1>电子元件库存管理 v1.0</h1>
|
||||
<a class="btn" href="{{ url_for('scan_page') }}">扫码/搜索</a>
|
||||
<div>
|
||||
<h1>{% if separate_mode %}{{ box_types[current_box_type].label }}{% else %}库存管理{% endif %}</h1>
|
||||
<p>{% if separate_mode %}当前为独立分类界面,减少长列表翻找成本{% else %}极简中性灰布局,聚焦数量/分类/变动核心信息{% endif %}</p>
|
||||
</div>
|
||||
<div class="hero-actions">
|
||||
<a class="btn btn-light" href="{{ url_for('types_page') }}">分类总览</a>
|
||||
<a class="btn btn-light" href="#quick-add">新增库存</a>
|
||||
<a class="btn btn-light" href="{{ url_for('stats_page') }}">统计页</a>
|
||||
<a class="btn" href="{{ url_for('scan_page') }}">扫码/搜索</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<div class="layout-shell">
|
||||
<aside class="catalog-sidebar panel">
|
||||
<h2>容器导航</h2>
|
||||
<nav class="catalog-nav">
|
||||
<aside class="catalog-sidebar">
|
||||
<button class="sidebar-toggle btn btn-light" type="button" aria-expanded="false" aria-controls="side-low-stock">低库存面板</button>
|
||||
<section class="panel" id="sidebar-nav-panel">
|
||||
<h2>容器导航</h2>
|
||||
<div class="card-actions icon-links" aria-label="快捷功能">
|
||||
<a class="icon-link" href="#sidebar-nav-panel" title="定位容器导航" aria-label="定位容器导航">
|
||||
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="6" width="16" height="12" rx="2"/><path d="M4 10h16"/><path d="M12 10v8"/></svg>
|
||||
</a>
|
||||
<a class="icon-link" href="{{ url_for('stats_page') }}" title="打开统计页" aria-label="打开统计页">
|
||||
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 18h16"/><path d="M6 14l4-4 4 2 4-5"/></svg>
|
||||
</a>
|
||||
<a class="icon-link" href="#quick-add" title="跳转新增库存" aria-label="跳转新增库存">
|
||||
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="8"/><path d="M12 8v8"/><path d="M8 12h8"/></svg>
|
||||
</a>
|
||||
<a class="icon-link" href="#side-low-stock" title="跳转低库存面板" aria-label="跳转低库存面板">
|
||||
<svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="5" y="5" width="6" height="6" rx="2"/><rect x="13" y="5" width="6" height="6" rx="2"/><rect x="5" y="13" width="6" height="6" rx="2"/><rect x="13" y="13" width="6" height="6" rx="2"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
<nav class="catalog-nav" id="catalog-nav-links">
|
||||
{% for key, meta in box_types.items() %}
|
||||
<a href="#group-{{ key }}">{{ meta.label }} ({{ groups[key]|length }})</a>
|
||||
<a href="{{ url_for('type_page', box_type=key) }}" class="{% if separate_mode and current_box_type == key %}active{% endif %}">{{ meta.label }} ({{ groups[key]|length }})</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
</section>
|
||||
|
||||
<section class="panel side-metrics">
|
||||
<h2>关键指标</h2>
|
||||
<div class="side-metrics-grid">
|
||||
<article class="metric-card">
|
||||
<p class="metric-title">容器总数</p>
|
||||
<p class="metric-value">{{ stats.box_count }}</p>
|
||||
</article>
|
||||
<article class="metric-card">
|
||||
<p class="metric-title">启用元件</p>
|
||||
<p class="metric-value">{{ stats.active_items }}</p>
|
||||
</article>
|
||||
<article class="metric-card">
|
||||
<p class="metric-title">近7天净变动</p>
|
||||
<p class="metric-value">{% if stats.period_net_change_7d > 0 %}+{% endif %}{{ stats.period_net_change_7d }}</p>
|
||||
</article>
|
||||
<article class="metric-card">
|
||||
<p class="metric-title">待补货元件</p>
|
||||
<p class="metric-value">{{ stats.low_stock_count }}</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel side-low-stock" id="side-low-stock">
|
||||
<h2>低库存元器件</h2>
|
||||
<ul class="side-low-stock-list">
|
||||
{% for item in low_stock_items %}
|
||||
<li>
|
||||
<div>
|
||||
<strong>{{ item.name }}</strong>
|
||||
<p class="hint">{{ item.part_no }} | {{ item.box_name }} / {{ item.slot_code }} | 数量 {{ item.quantity }}</p>
|
||||
</div>
|
||||
<a class="btn btn-light" href="{{ item.edit_url }}">编辑</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="muted">当前没有低库存元器件。</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<section class="catalog-content">
|
||||
<h2>容器列表</h2>
|
||||
|
||||
{% for key, meta in box_types.items() %}
|
||||
{% if view_box_types is defined %}
|
||||
{% set display_box_types = view_box_types %}
|
||||
{% else %}
|
||||
{% set display_box_types = box_types.keys() %}
|
||||
{% endif %}
|
||||
{% for key in display_box_types %}
|
||||
{% set meta = box_types[key] %}
|
||||
<section class="group-panel panel" id="group-{{ key }}">
|
||||
<div class="group-title-row">
|
||||
<h3>{{ meta.label }}</h3>
|
||||
<span class="group-desc">{{ meta.default_desc }}</span>
|
||||
</div>
|
||||
|
||||
<form class="new-box-form" method="post" action="{{ url_for('create_box') }}">
|
||||
{% if key != 'bag' %}
|
||||
<form class="new-box-form" method="post" action="{{ url_for('create_box') }}" {% if loop.first %}id="quick-add"{% endif %}>
|
||||
<input type="hidden" name="box_type" value="{{ key }}">
|
||||
<input type="text" name="name" placeholder="基础名称(自动拼范围)" required>
|
||||
{% if separate_mode %}<input type="hidden" name="return_to_type" value="{{ current_box_type }}">{% endif %}
|
||||
<input type="text" name="name" placeholder="基础名称(自动拼范围)" required aria-label="基础名称">
|
||||
<input type="text" name="slot_prefix" placeholder="前缀(如A/B/C)">
|
||||
<input type="number" name="start_number" min="0" value="1" placeholder="起始序号">
|
||||
<input type="text" name="description" placeholder="备注(可选)">
|
||||
@@ -43,20 +115,31 @@
|
||||
<button class="btn" type="submit">新增盒子</button>
|
||||
<span class="hint suggest-preview"></span>
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="hint">袋装清单为固定容器(大盒),不需要新增盒子。</p>
|
||||
{% endif %}
|
||||
|
||||
<section class="box-list">
|
||||
{% for item in groups[key] %}
|
||||
<article class="box-card">
|
||||
<h4>{{ item.box.name }}</h4>
|
||||
<p>{{ item.box.description or '暂无描述' }}</p>
|
||||
{% if item.box.box_type == 'bag' %}
|
||||
<p>编号前缀: {{ item.box.slot_prefix }} | 袋装清单不使用范围</p>
|
||||
<p>已记录: {{ item.used_count }} 项</p>
|
||||
{% else %}
|
||||
<p>编号前缀: {{ item.box.slot_prefix }} | 范围: {{ item.slot_range }}</p>
|
||||
<p>已启用: {{ item.used_count }}/{{ item.box.slot_capacity }}</p>
|
||||
{% endif %}
|
||||
|
||||
<div class="card-actions">
|
||||
<a class="btn" href="{{ url_for('view_box', box_id=item.box.id) }}">进入列表</a>
|
||||
<form method="post" action="{{ url_for('delete_box', box_id=item.box.id) }}" onsubmit="return confirm('确认删除盒子及内部全部记录吗?')">
|
||||
{% if item.box.box_type != 'bag' %}
|
||||
<form method="post" action="{{ url_for('delete_box', box_id=item.box.id) }}" onsubmit="return confirm('确认删除这个盒子及其内部记录吗?')">
|
||||
{% if separate_mode %}<input type="hidden" name="return_to_type" value="{{ current_box_type }}">{% endif %}
|
||||
<button class="btn btn-danger" type="submit">删除</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<details class="box-overview">
|
||||
@@ -76,6 +159,7 @@
|
||||
<summary>设置(改名/前缀/起始号)</summary>
|
||||
<p class="hint">输入基础名称后,系统会自动生成: 基础名称 + 编号范围。</p>
|
||||
<form class="new-box-form compact" method="post" action="{{ url_for('update_box', box_id=item.box.id) }}">
|
||||
{% if separate_mode %}<input type="hidden" name="return_to_type" value="{{ current_box_type }}">{% endif %}
|
||||
<input type="text" name="name" value="{{ item.base_name }}" required>
|
||||
<input type="text" name="slot_prefix" value="{{ item.box.slot_prefix }}" required>
|
||||
<input type="number" name="start_number" min="0" value="{{ item.box.start_number }}" required>
|
||||
@@ -98,6 +182,69 @@
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
function bindSidebarCompactMode() {
|
||||
var sidebar = document.querySelector('.catalog-sidebar');
|
||||
var toggleBtn = document.querySelector('.sidebar-toggle');
|
||||
if (!sidebar) {
|
||||
return;
|
||||
}
|
||||
var storageKey = 'inventorySidebarManualExpand';
|
||||
var manualExpand = false;
|
||||
|
||||
try {
|
||||
manualExpand = localStorage.getItem(storageKey) === '1';
|
||||
} catch (e) {
|
||||
manualExpand = false;
|
||||
}
|
||||
|
||||
function updateToggleState() {
|
||||
if (!toggleBtn) {
|
||||
return;
|
||||
}
|
||||
toggleBtn.setAttribute('aria-expanded', manualExpand ? 'true' : 'false');
|
||||
toggleBtn.textContent = manualExpand ? '收起低库存面板' : '展开低库存面板';
|
||||
}
|
||||
|
||||
function syncCompactState() {
|
||||
var shouldCompact = window.innerWidth > 980 && window.scrollY > 220;
|
||||
sidebar.classList.toggle('compact', shouldCompact);
|
||||
sidebar.classList.toggle('manual-expand', shouldCompact && manualExpand);
|
||||
updateToggleState();
|
||||
}
|
||||
|
||||
if (toggleBtn) {
|
||||
toggleBtn.addEventListener('click', function () {
|
||||
manualExpand = !manualExpand;
|
||||
try {
|
||||
localStorage.setItem(storageKey, manualExpand ? '1' : '0');
|
||||
} catch (e) {
|
||||
// ignore storage errors
|
||||
}
|
||||
syncCompactState();
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', syncCompactState, { passive: true });
|
||||
window.addEventListener('resize', syncCompactState);
|
||||
syncCompactState();
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
var stack = document.querySelector('.toast-stack');
|
||||
if (!stack) {
|
||||
stack = document.createElement('div');
|
||||
stack.className = 'toast-stack';
|
||||
document.body.appendChild(stack);
|
||||
}
|
||||
var toast = document.createElement('div');
|
||||
toast.className = 'toast';
|
||||
toast.textContent = message;
|
||||
stack.appendChild(toast);
|
||||
setTimeout(function () {
|
||||
toast.remove();
|
||||
}, 1600);
|
||||
}
|
||||
|
||||
function updateSuggest(form, payload) {
|
||||
var startInput = form.querySelector('input[name="start_number"]');
|
||||
var prefixInput = form.querySelector('input[name="slot_prefix"]');
|
||||
@@ -112,6 +259,7 @@
|
||||
if (preview) {
|
||||
preview.textContent = '建议范围: ' + payload.preview_range;
|
||||
}
|
||||
showToast('已更新建议起始号');
|
||||
}
|
||||
|
||||
document.querySelectorAll('.suggest-start-btn').forEach(function (btn) {
|
||||
@@ -139,16 +287,18 @@
|
||||
.then(function (resp) { return resp.json(); })
|
||||
.then(function (data) {
|
||||
if (!data.ok) {
|
||||
alert(data.message || '建议起始号失败');
|
||||
showToast(data.message || '建议起始号失败');
|
||||
return;
|
||||
}
|
||||
updateSuggest(form, data);
|
||||
})
|
||||
.catch(function () {
|
||||
alert('建议起始号失败,请稍后重试');
|
||||
showToast('建议起始号失败,请稍后重试');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
bindSidebarCompactMode();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user