- 增加了 LCSC API 集成,可利用 app_id、access_key 和 secret_key 获取产品详情。 - 实现了用于安全 API 请求的一次性和签名生成。 - 通过新端点提升包容量管理,更新插槽容量。 - 更新界面,支持 LCSC 产品直接导入袋口。 - 改进了 API 响应和用户输入验证的错误处理。 - 重构箱子渲染逻辑,以适应新的包包功能和展示产品详情。 - 为与 LCSC 产品信息相关的新 UI 元素添加了 CSS 样式。 - 更新了 AI 设置页面,包含了 LCSC API 配置选项。
219 lines
9.1 KiB
HTML
219 lines
9.1 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">
|
|
<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="{{ url_for('search_page') }}">快速搜索</a>
|
|
<a class="btn btn-light" href="#quick-add">新增库存</a>
|
|
<a class="btn btn-light" href="{{ url_for('stats_page') }}">统计页</a>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="container">
|
|
<div class="layout-shell">
|
|
<aside class="catalog-sidebar">
|
|
<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>
|
|
</div>
|
|
<nav class="catalog-nav" id="catalog-nav-links">
|
|
{% for key, meta in box_types.items() %}
|
|
<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>
|
|
</aside>
|
|
|
|
<section class="catalog-content">
|
|
<h2>容器列表</h2>
|
|
|
|
{% 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 loop.first %}id="quick-add"{% endif %}>
|
|
<input type="hidden" name="box_type" value="{{ key }}">
|
|
{% 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)">
|
|
{% if key == 'custom' %}
|
|
<input type="number" name="slot_capacity" min="1" value="{{ meta.default_capacity }}" placeholder="格数" required>
|
|
{% endif %}
|
|
<input type="number" name="start_number" min="0" value="1" placeholder="起始序号">
|
|
<input type="text" name="description" placeholder="备注(可选)">
|
|
<button class="btn btn-light suggest-start-btn" type="button" data-box-type="{{ key }}">建议起始号</button>
|
|
<button class="btn" type="submit">新增盒子</button>
|
|
<span class="hint suggest-preview"></span>
|
|
</form>
|
|
|
|
<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 == 'custom' %}
|
|
<p>格数: {{ item.box.slot_capacity }} | 编号前缀: {{ item.box.slot_prefix }} | 范围: {{ item.slot_range }}</p>
|
|
<p>已启用: {{ item.used_count }}/{{ item.box.slot_capacity }}</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 separate_mode %}<input type="hidden" name="return_to_type" value="{{ current_box_type }}">{% endif %}
|
|
<button class="btn btn-danger" type="submit">删除</button>
|
|
</form>
|
|
</div>
|
|
|
|
<details class="box-overview">
|
|
<summary>概览(已启用序号和名称)</summary>
|
|
{% if item.overview_rows %}
|
|
<ul>
|
|
{% for row in item.overview_rows %}
|
|
<li>{{ row.slot_code }} - {{ row.name }} ({{ row.part_no }})</li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}
|
|
<p>暂无启用记录</p>
|
|
{% endif %}
|
|
</details>
|
|
|
|
<details class="box-overview">
|
|
<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>
|
|
{% if item.box.box_type == 'custom' %}
|
|
<input type="number" name="slot_capacity" min="1" value="{{ item.box.slot_capacity }}" required>
|
|
{% endif %}
|
|
<input type="number" name="start_number" min="0" value="{{ item.box.start_number }}" required>
|
|
<input type="text" name="description" value="{{ item.box.description or '' }}">
|
|
<button class="btn btn-light suggest-start-btn" type="button" data-box-id="{{ item.box.id }}" data-box-type="{{ item.box.box_type }}">建议起始号</button>
|
|
<button class="btn" type="submit">保存设置</button>
|
|
<span class="hint suggest-preview"></span>
|
|
</form>
|
|
</details>
|
|
</article>
|
|
{% else %}
|
|
<p>当前分类还没有盒子,先新增一个。</p>
|
|
{% endfor %}
|
|
</section>
|
|
</section>
|
|
{% endfor %}
|
|
</section>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
(function () {
|
|
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"]');
|
|
var preview = form.querySelector('.suggest-preview');
|
|
|
|
if (startInput) {
|
|
startInput.value = payload.start_number;
|
|
}
|
|
if (prefixInput && !prefixInput.value.trim()) {
|
|
prefixInput.value = payload.slot_prefix;
|
|
}
|
|
if (preview) {
|
|
preview.textContent = '建议范围: ' + payload.preview_range;
|
|
}
|
|
showToast('已更新建议起始号');
|
|
}
|
|
|
|
document.querySelectorAll('.suggest-start-btn').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
var form = btn.closest('form');
|
|
if (!form) {
|
|
return;
|
|
}
|
|
|
|
var prefixInput = form.querySelector('input[name="slot_prefix"]');
|
|
var boxType = btn.dataset.boxType || 'small_28';
|
|
var boxId = btn.dataset.boxId || '';
|
|
var prefix = prefixInput ? prefixInput.value.trim() : '';
|
|
var slotCapacityInput = form.querySelector('input[name="slot_capacity"]');
|
|
var slotCapacity = slotCapacityInput ? slotCapacityInput.value.trim() : '';
|
|
|
|
var params = new URLSearchParams();
|
|
params.set('box_type', boxType);
|
|
if (prefix) {
|
|
params.set('slot_prefix', prefix);
|
|
}
|
|
if (boxId) {
|
|
params.set('box_id', boxId);
|
|
}
|
|
if (slotCapacity) {
|
|
params.set('slot_capacity', slotCapacity);
|
|
}
|
|
|
|
fetch('{{ url_for('suggest_start') }}?' + params.toString())
|
|
.then(function (resp) { return resp.json(); })
|
|
.then(function (data) {
|
|
if (!data.ok) {
|
|
showToast(data.message || '建议起始号失败');
|
|
return;
|
|
}
|
|
updateSuggest(form, data);
|
|
})
|
|
.catch(function () {
|
|
showToast('建议起始号失败,请稍后重试');
|
|
});
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|