Files
inventory/templates/types.html

250 lines
10 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.
<!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>仓库概览</h1>
<p>先看关键指标与待补货,再进入对应分类处理</p>
</div>
<div class="hero-actions">
<a class="btn" href="{{ url_for('type_page', box_type='custom') }}#quick-add">添加容器</a>
<a class="btn btn-light" href="{{ url_for('search_page') }}">快速搜索</a>
<a class="btn btn-light" href="{{ url_for('stats_page') }}">统计页</a>
</div>
</header>
<main class="container">
{% if error %}
<p class="alert">{{ error }}</p>
{% endif %}
{% if notice %}
<p class="notice">{{ notice }}</p>
{% endif %}
<div class="overview-layout">
<section class="overview-main">
<section class="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">待补货元件</p>
<p class="metric-value">{{ stats.low_stock_count }}</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>
</section>
<section class="metrics-grid">
{% for item in type_cards %}
<article class="metric-card type-card">
<div class="type-card-head">
<p class="metric-title">{{ item.label }}</p>
<a class="type-card-more" href="{{ url_for('edit_container_type', box_type=item.key) }}" aria-label="编辑容器属性" title="编辑容器属性">...</a>
</div>
<p class="metric-value">{{ item.count }}</p>
<p class="hint">{{ item.desc }}</p>
<p class="hint">容器 {{ item.count }} 个 | 启用元件 {{ item.item_count }} 种 | 总库存 {{ item.quantity }}</p>
<a class="btn" href="{{ item.url }}">进入分类</a>
</article>
{% endfor %}
</section>
<section class="panel side-low-stock" id="overview-low-stock">
<h2>低库存元器件</h2>
{% for group in low_stock_groups %}
<h3>{{ group['label'] }}{{ group['items']|length }}</h3>
<ul class="side-low-stock-list">
{% for item in group['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>
{% endfor %}
</section>
</section>
<aside class="overview-sidebar">
<section class="panel ai-panel" id="ai-panel">
<div class="ai-panel-head">
<h2>AI补货建议</h2>
<a class="btn btn-light" href="{{ url_for('ai_settings_page') }}">参数</a>
</div>
<p class="hint">根据低库存与近30天出库数据生成可执行补货建议。</p>
<button class="btn" id="ai-restock-btn" type="button">生成建议</button>
<p class="hint" id="ai-panel-status"></p>
<p class="hint" id="ai-panel-warning"></p>
<div id="ai-plan-groups" class="ai-plan-groups"></div>
<details class="box-overview" id="ai-raw-wrap" hidden>
<summary>查看原始 AI 文本</summary>
<pre id="ai-panel-content" class="ai-panel-content"></pre>
</details>
</section>
</aside>
</div>
</main>
<script>
(function () {
var aiBtn = document.getElementById('ai-restock-btn');
var statusNode = document.getElementById('ai-panel-status');
var warningNode = document.getElementById('ai-panel-warning');
var contentNode = document.getElementById('ai-panel-content');
var planGroups = document.getElementById('ai-plan-groups');
var rawWrap = document.getElementById('ai-raw-wrap');
function clearPlan() {
if (planGroups) {
planGroups.innerHTML = '';
}
}
function renderPlan(plan) {
if (!planGroups) {
return;
}
clearPlan();
var groups = [
{ key: 'urgent', title: '紧急补货' },
{ key: 'this_week', title: '本周建议补货' },
{ key: 'defer', title: '暂缓补货' }
];
groups.forEach(function (groupMeta) {
var rows = (plan && plan[groupMeta.key]) || [];
var wrap = document.createElement('section');
wrap.className = 'ai-plan-group';
var title = document.createElement('h3');
title.textContent = groupMeta.title + '' + rows.length + '';
wrap.appendChild(title);
var list = document.createElement('ul');
list.className = 'side-low-stock-list';
if (!rows.length) {
var empty = document.createElement('li');
empty.className = 'muted';
empty.textContent = '暂无条目';
list.appendChild(empty);
} else {
rows.forEach(function (item) {
var li = document.createElement('li');
var content = document.createElement('div');
var strong = document.createElement('strong');
strong.textContent = (item.name || '未命名元件') + ' (' + (item.part_no || '-') + ')';
content.appendChild(strong);
var qty = document.createElement('p');
qty.className = 'hint';
qty.textContent = '建议补货: ' + (item.suggest_qty || '待确认');
content.appendChild(qty);
var reason = document.createElement('p');
reason.className = 'hint';
reason.textContent = '理由: ' + (item.reason || '无');
content.appendChild(reason);
li.appendChild(content);
list.appendChild(li);
});
}
wrap.appendChild(list);
planGroups.appendChild(wrap);
});
}
if (!aiBtn) {
return;
}
aiBtn.addEventListener('click', function () {
aiBtn.disabled = true;
statusNode.textContent = '正在生成建议,请稍候...';
if (warningNode) {
warningNode.textContent = '';
}
clearPlan();
if (contentNode) {
contentNode.textContent = '';
}
if (rawWrap) {
rawWrap.hidden = true;
}
fetch('{{ url_for('ai_restock_plan') }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: '{}'
})
.then(function (resp) {
return resp.json().then(function (data) {
return { ok: resp.ok, data: data };
});
})
.then(function (result) {
var data = result.data || {};
if (!result.ok || !data.ok) {
statusNode.textContent = '生成失败';
if (warningNode) {
warningNode.textContent = data.message || 'AI服务暂时不可用';
}
if (data.plan) {
renderPlan(data.plan);
}
return;
}
statusNode.textContent = (data.plan && data.plan.summary) || '建议已生成';
if (warningNode) {
warningNode.textContent = data.parse_warning || '';
}
renderPlan(data.plan || {});
if (contentNode && data.suggestion) {
contentNode.textContent = data.suggestion;
if (rawWrap) {
rawWrap.hidden = false;
}
}
})
.catch(function () {
statusNode.textContent = '生成失败';
if (warningNode) {
warningNode.textContent = '请求失败,请稍后重试';
}
})
.finally(function () {
aiBtn.disabled = false;
});
});
})();
</script>
</body>
</html>